Commit 28379b664a7167f5fa9625a9c1b24b5366b23d5d
1 parent
27f0d5bd
Exists in
better_forms
copied from amda_xml_manager project
Showing
22 changed files
with
1683 additions
and
294 deletions
Show diff stats
xsd2tkform/core/annotation.py
@@ -7,6 +7,8 @@ class Annotation: | @@ -7,6 +7,8 @@ class Annotation: | ||
7 | self.documentation = docs # dictionary where keys are language tags and values are documentation text | 7 | self.documentation = docs # dictionary where keys are language tags and values are documentation text |
8 | def languages(self): | 8 | def languages(self): |
9 | return [k for k in self.documentation] | 9 | return [k for k in self.documentation] |
10 | + def __eq__(self, e): | ||
11 | + return self.docs==e.docs | ||
10 | def __str__(self): | 12 | def __str__(self): |
11 | return "Annotation(languages={})".format(self.languages()) | 13 | return "Annotation(languages={})".format(self.languages()) |
12 | 14 |
xsd2tkform/core/choice.py
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | from .element import Element | 4 | from .element import Element |
5 | 5 | ||
6 | class Choice: | 6 | class Choice: |
7 | - def __init__(self, min_occurs=0, max_occurs=0): | 7 | + def __init__(self, min_occurs=1, max_occurs=1): |
8 | self.min_occurs=min_occurs | 8 | self.min_occurs=min_occurs |
9 | self.max_occurs=max_occurs | 9 | self.max_occurs=max_occurs |
10 | self.elements=[] | 10 | self.elements=[] |
xsd2tkform/core/element.py
1 | """Element definition | 1 | """Element definition |
2 | """ | 2 | """ |
3 | +from lxml import etree | ||
4 | + | ||
5 | +class Attribute: | ||
6 | + def __init__(self, name=None, type="string", use=None, default=None): | ||
7 | + self.name=name | ||
8 | + self.type=type | ||
9 | + self.use=use | ||
10 | + self.default=default | ||
11 | + def mandatory(self): | ||
12 | + return self.use=="required" | ||
13 | + def __eq__(self, e): | ||
14 | + if not isinstance(e, Attribute): | ||
15 | + return False | ||
16 | + return self.name==e.name and self.type==e.type and self.use==e.use and self.default==e.default | ||
17 | + def __str__(self): | ||
18 | + return "Attribute (name={}, type={}, use={}, default={})".format(self.name, self.type, self.use, self.default) | ||
19 | + @staticmethod | ||
20 | + def from_element(element): | ||
21 | + att=dict(element.attrib) | ||
22 | + return Attribute(**att) | ||
23 | + | ||
24 | +class AnyElement: | ||
25 | + def __init__(self, **kwargs): | ||
26 | + self.attributes=kwargs | ||
27 | + def __str__(self, *args, **kwargs): | ||
28 | + return "AnyElement" | ||
3 | 29 | ||
4 | class Element: | 30 | class Element: |
5 | - def __init__(self, name=None, etype=None, min_occurs=0, max_occurs=0): | 31 | + def __init__(self, name=None, etype=None, min_occurs=1, max_occurs=1, abstract=False, typedef=None, ref=None, substitution_group=None,**kwargs): |
6 | self.name=name | 32 | self.name=name |
7 | self.type=etype | 33 | self.type=etype |
8 | self.min_occurs=min_occurs | 34 | self.min_occurs=min_occurs |
9 | self.max_occurs=max_occurs | 35 | self.max_occurs=max_occurs |
36 | + self.typedef = typedef | ||
37 | + self.abstract=abstract | ||
38 | + self.ref=ref | ||
39 | + self.substitution_group=substitution_group | ||
40 | + if self.type is None: | ||
41 | + if self.typedef is None: | ||
42 | + self.type = self.name | ||
43 | + else: | ||
44 | + self.type = self.typedef.name | ||
45 | + def __eq__(self, e): | ||
46 | + return self.name==e.name and self.type==e.type and self.min_occurs==e.min_occurs and \ | ||
47 | + self.max_occurs==e.max_occurs and self.typedef==e.typedef and self.abstract==e.abstract and \ | ||
48 | + self.ref==e.ref and self.substitution_group==e.substitution_group | ||
10 | def __str__(self, tab_n=0): | 49 | def __str__(self, tab_n=0): |
11 | - return "\t"*tab_n+"Element(name={}, type={}, min_occurs={}, max_occurs={})".format(self.name, | ||
12 | - self.type,self.min_occurs, self.max_occurs) | 50 | + return "\t"*tab_n+"Element(name={}, type={}, min_occurs={}, max_occurs={}, abstract={}, typedef={}, ref={})".format(self.name, |
51 | + self.type,self.min_occurs, self.max_occurs, self.abstract, self.typedef, self.ref) | ||
13 | @staticmethod | 52 | @staticmethod |
14 | def from_element(element): | 53 | def from_element(element): |
15 | att=dict(element.attrib) | 54 | att=dict(element.attrib) |
@@ -23,4 +62,44 @@ class Element: | @@ -23,4 +62,44 @@ class Element: | ||
23 | att["max_occurs"]=att.pop("maxOccurs") | 62 | att["max_occurs"]=att.pop("maxOccurs") |
24 | if att["max_occurs"].isdigit(): | 63 | if att["max_occurs"].isdigit(): |
25 | att["max_occurs"]=int(att["max_occurs"]) | 64 | att["max_occurs"]=int(att["max_occurs"]) |
26 | - return Element(**att) | 65 | + if "substitutionGroup" in att: |
66 | + att["substitution_group"]=att.pop("substitutionGroup") | ||
67 | + if "abstract" in att: | ||
68 | + print("ABSTRACT val ", att["abstract"], type(att["abstract"])) | ||
69 | + if att["abstract"]=="true": | ||
70 | + att["abstract"]=True | ||
71 | + if att["abstract"]=="false": | ||
72 | + att["abstract"]=False | ||
73 | + # check if element contains type definition | ||
74 | + ct=None | ||
75 | + for child in element: | ||
76 | + if child.tag.endswith("complexType"): | ||
77 | + from .type import ComplexType, SimpleType | ||
78 | + from .utils import get_sequence, get_extension | ||
79 | + from .sequence import Sequence | ||
80 | + sequence = get_sequence(child) | ||
81 | + attributes= get_attributes(child) | ||
82 | + extension=get_extension(child) | ||
83 | + if extension is None: | ||
84 | + ct=ComplexType(att["name"], annotation=None, sequence=sequence, attributes=attributes, extension=extension) | ||
85 | + elif isinstance(extension[1], Sequence): | ||
86 | + ct=ComplexType(att["name"], annotation=None, sequence=sequence, attributes=attributes, extension=extension) | ||
87 | + elif isinstance(extension[1], list): | ||
88 | + from .restriction import Restriction | ||
89 | + ct=SimpleType(att["name"], restriction=Restriction(base=extension[0]), attributes=extension[1]) | ||
90 | + else: | ||
91 | + ct=ComplexType(att["name"], annotation=None, sequence=sequence, attributes=attributes, extension=extension) | ||
92 | + continue | ||
93 | + | ||
94 | + | ||
95 | + | ||
96 | + return Element(typedef = ct, **att) | ||
97 | + | ||
98 | +def get_attributes(element): | ||
99 | + attributes=[] | ||
100 | + for child in element: | ||
101 | + if isinstance(child, etree._Comment): | ||
102 | + continue | ||
103 | + if child.tag.endswith("}attribute"): | ||
104 | + attributes.append(Attribute.from_element(child)) | ||
105 | + return attributes |
xsd2tkform/core/parser.py
@@ -4,9 +4,9 @@ For now we suppose the file contains a single schema | @@ -4,9 +4,9 @@ For now we suppose the file contains a single schema | ||
4 | """ | 4 | """ |
5 | from lxml import etree | 5 | from lxml import etree |
6 | 6 | ||
7 | -from xsd2tkform.core.type import SimpleType, ComplexType, Group, from_element | ||
8 | -from xsd2tkform.core.annotation import Annotation | ||
9 | -from xsd2tkform.core.schema import Schema | 7 | +from .type import SimpleType, ComplexType, Group, from_element |
8 | +from .annotation import Annotation | ||
9 | +from .schema import Schema | ||
10 | 10 | ||
11 | class XSDParser: | 11 | class XSDParser: |
12 | def __init__(self, filename=None): | 12 | def __init__(self, filename=None): |
@@ -21,7 +21,10 @@ class XSDParser: | @@ -21,7 +21,10 @@ class XSDParser: | ||
21 | ns=root.nsmap | 21 | ns=root.nsmap |
22 | # if root is a schema | 22 | # if root is a schema |
23 | if root.tag.endswith("schema"): | 23 | if root.tag.endswith("schema"): |
24 | - self.schemas.append(Schema.from_element(root)) | 24 | + self.schemas.append(Schema.from_element(root, filename)) |
25 | + # apply substitutions | ||
26 | + self.schemas[-1].apply_substitutions() | ||
27 | + self.schemas[-1].apply_extensions() | ||
25 | return | 28 | return |
26 | def get(self, typename): | 29 | def get(self, typename): |
27 | for schema in self.schemas: | 30 | for schema in self.schemas: |
xsd2tkform/core/restriction.py
@@ -6,6 +6,8 @@ class Restriction: | @@ -6,6 +6,8 @@ class Restriction: | ||
6 | def __init__(self, base=None, enum=[]): | 6 | def __init__(self, base=None, enum=[]): |
7 | self.base=base | 7 | self.base=base |
8 | self.enum=enum | 8 | self.enum=enum |
9 | + def __eq__(self, e): | ||
10 | + return self.base==e.base and self.enum==e.enum | ||
9 | def __str__(self): | 11 | def __str__(self): |
10 | return "Restriction(base={}, enum={})".format(self.base, self.enum) | 12 | return "Restriction(base={}, enum={})".format(self.base, self.enum) |
11 | def possible_values(self): | 13 | def possible_values(self): |
xsd2tkform/core/schema.py
@@ -2,27 +2,116 @@ | @@ -2,27 +2,116 @@ | ||
2 | """ | 2 | """ |
3 | from lxml import etree | 3 | from lxml import etree |
4 | 4 | ||
5 | -from xsd2tkform.core.type import SimpleType, ComplexType, Group, from_element | ||
6 | -from xsd2tkform.core.element import Element | 5 | +from .type import SimpleType, ComplexType, Group, from_element |
6 | +from .element import Element | ||
7 | 7 | ||
8 | class Schema: | 8 | class Schema: |
9 | - def __init__(self): | 9 | + def __init__(self, filename): |
10 | + self.filename=filename # need filename to manage include statements | ||
11 | + self.root_items = [] | ||
10 | self.items=[] | 12 | self.items=[] |
13 | + #self.elements={} # map element names to their type | ||
11 | self.simple_types={} | 14 | self.simple_types={} |
12 | self.complex_types={} | 15 | self.complex_types={} |
13 | self.groups={} | 16 | self.groups={} |
14 | - def parse_element(self, element): | 17 | + def find_ref(self, name): |
18 | + for i in self.items: | ||
19 | + if i.name==name: | ||
20 | + return i | ||
21 | + def find_substitutions(self, name): | ||
22 | + return [e for e in self.items if e.substitution_group==name] | ||
23 | + def include_schema(self, element): | ||
24 | + schema_file = element.attrib["schemaLocation"] | ||
25 | + from .parser import XSDParser | ||
26 | + import os | ||
27 | + path = os.path.join(os.path.dirname(self.filename), schema_file) | ||
28 | + parser = XSDParser(path) | ||
29 | + for schema in parser.schemas: | ||
30 | + self.add_schema(schema) | ||
31 | + def add_schema(self, schema): | ||
32 | + for i in schema.items: | ||
33 | + if not i in self.items: | ||
34 | + self.items.append(i) | ||
35 | + for ri in schema.root_items: | ||
36 | + if not ri in self.root_items: | ||
37 | + self.root_items.append(ri) | ||
38 | + # simple types | ||
39 | + for k in schema.simple_types: | ||
40 | + self.simple_types[k]=schema.simple_types[k] | ||
41 | + # complex types | ||
42 | + for k in schema.complex_types: | ||
43 | + self.complex_types[k]=schema.complex_types[k] | ||
44 | + # groups | ||
45 | + for k in schema.groups: | ||
46 | + self.groups[k]=schema.groups[k] | ||
47 | + | ||
48 | + | ||
49 | + | ||
50 | + def parse_element(self, element, root=True, name=None): | ||
51 | + # if element is an include statement | ||
52 | + if element.tag.endswith("}include"): | ||
53 | + self.include_schema(element) | ||
15 | td = from_element(element) | 54 | td = from_element(element) |
55 | + if td is not None: | ||
56 | + if td.name=="get": | ||
57 | + pass | ||
16 | if isinstance(td, SimpleType): | 58 | if isinstance(td, SimpleType): |
17 | self.simple_types[td.name]=td | 59 | self.simple_types[td.name]=td |
18 | elif isinstance(td, ComplexType): | 60 | elif isinstance(td, ComplexType): |
19 | self.complex_types[td.name]=td | 61 | self.complex_types[td.name]=td |
20 | elif isinstance(td, Group): | 62 | elif isinstance(td, Group): |
21 | self.groups[td.name]=td | 63 | self.groups[td.name]=td |
64 | + | ||
22 | elif isinstance(td, Element): | 65 | elif isinstance(td, Element): |
66 | + if td.type is None: | ||
67 | + # get type definition in this element | ||
68 | + td.type=td.name | ||
69 | + ct = td.typedef | ||
70 | + self.complex_types[ct.name]=ct | ||
71 | + #for child in element: | ||
72 | + # self.parse_element(child, name=td.name, root=False) | ||
73 | + #td.type = td.name | ||
74 | + | ||
23 | self.items.append(td) | 75 | self.items.append(td) |
76 | + if root: | ||
77 | + self.root_items.append(td) | ||
24 | else: | 78 | else: |
25 | - print("Unknown type defined : ", element, element.attrib, element.tag) | 79 | + pass |
80 | + | ||
81 | + # WARNING : should not do this | ||
82 | + for child in element: | ||
83 | + if isinstance(child, etree._Comment): | ||
84 | + continue | ||
85 | + self.parse_element(child, root=False) | ||
86 | + def apply_substitutions(self): | ||
87 | + for i in self.items: | ||
88 | + if i.substitution_group is not None: | ||
89 | + # get the current object type definition | ||
90 | + td = self.get(i.type) | ||
91 | + # get the base type | ||
92 | + bases = [e for e in self.items if e.name==i.substitution_group] | ||
93 | + if len(bases): | ||
94 | + # get the corresponding type | ||
95 | + base_t = self.get(bases[0].type) | ||
96 | + for att in base_t.attributes: | ||
97 | + if not att in td.attributes: | ||
98 | + td.attributes.append(att) | ||
99 | + self.complex_types[td.name]=td | ||
100 | + def apply_extensions(self): | ||
101 | + for k in self.complex_types: | ||
102 | + t = self.complex_types[k] | ||
103 | + if t.extension is not None: | ||
104 | + # get base type | ||
105 | + base_type = self.get(t.extension[0]) | ||
106 | + # add attributes and sequence element | ||
107 | + for att in base_type.attributes: | ||
108 | + if not att in t.attributes: | ||
109 | + t.attributes.append(att) | ||
110 | + for item in base_type.sequence.items: | ||
111 | + if not item in t.sequence.items: | ||
112 | + t.sequence.add(item) | ||
113 | + t.sequence = t.extension[1] | ||
114 | + t.extension=None | ||
26 | def get(self, typename): | 115 | def get(self, typename): |
27 | if ":" in typename: | 116 | if ":" in typename: |
28 | typename = typename.split(":")[-1] | 117 | typename = typename.split(":")[-1] |
@@ -32,6 +121,10 @@ class Schema: | @@ -32,6 +121,10 @@ class Schema: | ||
32 | return self.complex_types[typename] | 121 | return self.complex_types[typename] |
33 | if typename in self.groups: | 122 | if typename in self.groups: |
34 | return self.groups[typename] | 123 | return self.groups[typename] |
124 | + for item in self.items: | ||
125 | + if typename==item.name: | ||
126 | + return item | ||
127 | + | ||
35 | def __contains__(self, typename): | 128 | def __contains__(self, typename): |
36 | if typename in self.simple_types: | 129 | if typename in self.simple_types: |
37 | return True | 130 | return True |
@@ -39,13 +132,16 @@ class Schema: | @@ -39,13 +132,16 @@ class Schema: | ||
39 | return True | 132 | return True |
40 | if typename in self.groups: | 133 | if typename in self.groups: |
41 | return True | 134 | return True |
135 | + item_names = [i.name for i in self.items] | ||
136 | + if typename in item_names: | ||
137 | + return True | ||
42 | return False | 138 | return False |
43 | 139 | ||
44 | @staticmethod | 140 | @staticmethod |
45 | - def from_element(element): | 141 | + def from_element(element, filename): |
46 | if not element.tag.endswith("schema"): | 142 | if not element.tag.endswith("schema"): |
47 | raise Exception("Cannot initialize Schema from {}".format(element)) | 143 | raise Exception("Cannot initialize Schema from {}".format(element)) |
48 | - schema = Schema() | 144 | + schema = Schema(filename=filename) |
49 | # load type definitions and components | 145 | # load type definitions and components |
50 | for child in element: | 146 | for child in element: |
51 | # ignore comments | 147 | # ignore comments |
xsd2tkform/core/sequence.py
@@ -2,14 +2,16 @@ | @@ -2,14 +2,16 @@ | ||
2 | 2 | ||
3 | """ | 3 | """ |
4 | 4 | ||
5 | -from xsd2tkform.core.element import Element | ||
6 | -from xsd2tkform.core.choice import Choice | 5 | +from .element import Element |
6 | +from .choice import Choice | ||
7 | 7 | ||
8 | class Sequence: | 8 | class Sequence: |
9 | def __init__(self, items=[]): | 9 | def __init__(self, items=[]): |
10 | self.items = items | 10 | self.items = items |
11 | def add(self, i): | 11 | def add(self, i): |
12 | self.items.append(i) | 12 | self.items.append(i) |
13 | + def __eq__(self, e): | ||
14 | + return self.items==e.items | ||
13 | def __str__(self, tab_n=0): | 15 | def __str__(self, tab_n=0): |
14 | r="\t"*tab_n+"Sequence" | 16 | r="\t"*tab_n+"Sequence" |
15 | if len(self.items): | 17 | if len(self.items): |
@@ -28,6 +30,8 @@ class Sequence: | @@ -28,6 +30,8 @@ class Sequence: | ||
28 | s.add(Element.from_element(child)) | 30 | s.add(Element.from_element(child)) |
29 | elif child.tag.endswith("choice"): | 31 | elif child.tag.endswith("choice"): |
30 | s.add(Choice.from_element(child)) | 32 | s.add(Choice.from_element(child)) |
33 | + elif child.tag.endswith("any"): | ||
34 | + s.add(AnyElement(**dict(child.attrib))) | ||
31 | else: | 35 | else: |
32 | pass | 36 | pass |
33 | return s | 37 | return s |
xsd2tkform/core/type.py
@@ -2,47 +2,68 @@ | @@ -2,47 +2,68 @@ | ||
2 | 2 | ||
3 | XSDType class is the base class for all XSD types, inherited by the XSDSimpleType, XSDComplexType classes""" | 3 | XSDType class is the base class for all XSD types, inherited by the XSDSimpleType, XSDComplexType classes""" |
4 | 4 | ||
5 | -from xsd2tkform.core.element import Element | ||
6 | -from xsd2tkform.core.annotation import Annotation | ||
7 | -from xsd2tkform.core.restriction import Restriction | ||
8 | -from xsd2tkform.core.list import List | ||
9 | -from xsd2tkform.core.utils import get_annotation, get_restriction, get_list, get_sequence | 5 | +from .element import Element, get_attributes |
6 | +from .annotation import Annotation | ||
7 | +from .restriction import Restriction | ||
8 | +from .list import List | ||
9 | +from .utils import get_annotation, get_restriction, get_list, get_sequence, get_extension | ||
10 | 10 | ||
11 | class XSDType: | 11 | class XSDType: |
12 | def __init__(self, name=None, annotation=None): | 12 | def __init__(self, name=None, annotation=None): |
13 | self.name = name | 13 | self.name = name |
14 | self.annotation = annotation | 14 | self.annotation = annotation |
15 | + def __eq__(self, e): | ||
16 | + if not isinstance(e, XSDType): | ||
17 | + return False | ||
18 | + return self.name==e.name and self.annotation==e.annotation | ||
15 | def __str__(self): | 19 | def __str__(self): |
16 | return "XSDType(name={})".format(self.name) | 20 | return "XSDType(name={})".format(self.name) |
17 | 21 | ||
18 | class SimpleType(XSDType): | 22 | class SimpleType(XSDType): |
19 | - def __init__(self, name=None, restriction=None, item_list=None, *args, **kwargs): | 23 | + def __init__(self, name=None, restriction=None, item_list=None, attributes=[], *args, **kwargs): |
20 | XSDType.__init__(self, name=name, *args, **kwargs) | 24 | XSDType.__init__(self, name=name, *args, **kwargs) |
21 | self.restriction=restriction | 25 | self.restriction=restriction |
22 | self.item_list = item_list | 26 | self.item_list = item_list |
27 | + self.attributes=attributes | ||
28 | + def __eq__(self, e): | ||
29 | + if not isinstance(e, SimpleType): | ||
30 | + return False | ||
31 | + return super().__eq__(e) and self.restriction==e.restriction and self.item_list==e.item_list and self.attributes==e.attributes | ||
23 | def __str__(self): | 32 | def __str__(self): |
24 | att=["name={})".format(self.name)] | 33 | att=["name={})".format(self.name)] |
25 | if self.restriction is not None: | 34 | if self.restriction is not None: |
26 | att.append("\n\trestriction={}".format(str(self.restriction))) | 35 | att.append("\n\trestriction={}".format(str(self.restriction))) |
27 | if self.item_list is not None: | 36 | if self.item_list is not None: |
28 | att.append("\n\tlist={}".format(self.item_list)) | 37 | att.append("\n\tlist={}".format(self.item_list)) |
38 | + if len(self.attributes): | ||
39 | + att.append("\n\tattributes={}".format(self.attributes)) | ||
29 | a = "SimpleType({}".format(", ".join(att)) | 40 | a = "SimpleType({}".format(", ".join(att)) |
30 | return a | 41 | return a |
31 | 42 | ||
32 | class ComplexType(XSDType): | 43 | class ComplexType(XSDType): |
33 | - def __init__(self, name=None, sequence=None, *args, **kwargs): | 44 | + def __init__(self, name=None, sequence=None, attributes=[], extension=None, *args, **kwargs): |
34 | XSDType.__init__(self, name=name, *args, **kwargs) | 45 | XSDType.__init__(self, name=name, *args, **kwargs) |
35 | self.sequence=sequence | 46 | self.sequence=sequence |
47 | + self.attributes=attributes | ||
48 | + self.extension=extension | ||
49 | + def __eq__(self, e): | ||
50 | + if not isinstance(e, ComplexType): | ||
51 | + return False | ||
52 | + return super().__eq__(e) and self.sequence==e.sequence | ||
36 | def __str__(self, tab_n=0): | 53 | def __str__(self, tab_n=0): |
37 | r = "ComplexType(name={})".format(self.name) | 54 | r = "ComplexType(name={})".format(self.name) |
38 | if len(self.sequence): | 55 | if len(self.sequence): |
39 | r+="\n"+self.sequence.__str__(tab_n=tab_n+1) | 56 | r+="\n"+self.sequence.__str__(tab_n=tab_n+1) |
57 | + if len(self.attributes): | ||
58 | + r+="\nAttributes {}".format(self.attributes) | ||
40 | return r | 59 | return r |
41 | 60 | ||
42 | class Group(XSDType): | 61 | class Group(XSDType): |
43 | def __init__(self, name=None, sequence=None, *args, **kwargs): | 62 | def __init__(self, name=None, sequence=None, *args, **kwargs): |
44 | self.name = name | 63 | self.name = name |
45 | self.sequence=sequence | 64 | self.sequence=sequence |
65 | + def __eq__(self, e): | ||
66 | + return super().__eq__(e) and self.name==e.name and self.sequence==e.sequence | ||
46 | def __str__(self, tab_n=0): | 67 | def __str__(self, tab_n=0): |
47 | r="Group (name={})".format(self.name) | 68 | r="Group (name={})".format(self.name) |
48 | if len(self.sequence): | 69 | if len(self.sequence): |
@@ -53,19 +74,31 @@ class Group(XSDType): | @@ -53,19 +74,31 @@ class Group(XSDType): | ||
53 | def from_element(element): | 74 | def from_element(element): |
54 | """Parse a XML element to XSDType object | 75 | """Parse a XML element to XSDType object |
55 | """ | 76 | """ |
77 | + if "name" not in element.attrib: | ||
78 | + return | ||
56 | name = element.attrib["name"] | 79 | name = element.attrib["name"] |
57 | annotation = get_annotation(element) | 80 | annotation = get_annotation(element) |
81 | + attributes = get_attributes(element) | ||
58 | if element.tag.endswith("simpleType"): | 82 | if element.tag.endswith("simpleType"): |
59 | restriction = get_restriction(element) | 83 | restriction = get_restriction(element) |
60 | l= get_list(element) | 84 | l= get_list(element) |
61 | - return SimpleType(name=name, annotation=annotation, restriction=restriction, item_list=l) | 85 | + return SimpleType(name=name, annotation=annotation, restriction=restriction, item_list=l, attributes=attributes) |
62 | elif element.tag.endswith("complexType"): | 86 | elif element.tag.endswith("complexType"): |
63 | sequence = get_sequence(element) | 87 | sequence = get_sequence(element) |
64 | - return ComplexType(name, annotation=annotation, sequence=sequence) | 88 | + extension = get_extension(element) |
89 | + if extension is None: | ||
90 | + return ComplexType(name, annotation=annotation, sequence=sequence, attributes=attributes) | ||
91 | + from .sequence import Sequence | ||
92 | + if isinstance(extension[1], Sequence): | ||
93 | + return ComplexType(name, annotation=annotation, sequence=sequence, attributes=attributes, extension=extension) | ||
94 | + if isinstance(extension[1], list): | ||
95 | + r=SimpleType(name, type=extensions[0], attributes=extension[1]) | ||
96 | + return r | ||
65 | elif element.tag.endswith("group"): | 97 | elif element.tag.endswith("group"): |
66 | sequence = get_sequence(element) | 98 | sequence = get_sequence(element) |
67 | return Group(name, annotation=annotation, sequence=sequence) | 99 | return Group(name, annotation=annotation, sequence=sequence) |
68 | elif element.tag.endswith("element"): | 100 | elif element.tag.endswith("element"): |
69 | - return Element.from_element(element) | 101 | + e= Element.from_element(element) |
102 | + return e | ||
70 | else: | 103 | else: |
71 | return None | 104 | return None |
xsd2tkform/core/utils.py
1 | -from xsd2tkform.core.annotation import Annotation | ||
2 | -from xsd2tkform.core.restriction import Restriction | ||
3 | -from xsd2tkform.core.list import List | ||
4 | -from xsd2tkform.core.sequence import Sequence | ||
5 | -from xsd2tkform.core.choice import Choice | ||
6 | -from xsd2tkform.core.element import Element | 1 | +from lxml import etree |
2 | + | ||
3 | +from .annotation import Annotation | ||
4 | +from .restriction import Restriction | ||
5 | +from .list import List | ||
6 | +from .sequence import Sequence | ||
7 | +from .choice import Choice | ||
8 | +from .element import Element, AnyElement | ||
7 | 9 | ||
8 | def get_annotation(element): | 10 | def get_annotation(element): |
9 | """Get annotations from a type definition element | 11 | """Get annotations from a type definition element |
@@ -39,6 +41,8 @@ def get_list(element): | @@ -39,6 +41,8 @@ def get_list(element): | ||
39 | def get_sequence(element): | 41 | def get_sequence(element): |
40 | sequence = [] | 42 | sequence = [] |
41 | for child in element: | 43 | for child in element: |
44 | + if isinstance(child, etree._Comment): | ||
45 | + continue | ||
42 | if child.tag.endswith("sequence"): | 46 | if child.tag.endswith("sequence"): |
43 | # children should be elements of choices | 47 | # children should be elements of choices |
44 | for gchild in child: | 48 | for gchild in child: |
@@ -46,8 +50,28 @@ def get_sequence(element): | @@ -46,8 +50,28 @@ def get_sequence(element): | ||
46 | sequence.append(Element.from_element(gchild)) | 50 | sequence.append(Element.from_element(gchild)) |
47 | elif gchild.tag.endswith("choice"): | 51 | elif gchild.tag.endswith("choice"): |
48 | sequence.append(Choice.from_element(gchild)) | 52 | sequence.append(Choice.from_element(gchild)) |
53 | + elif gchild.tag.endswith("any"): | ||
54 | + sequence.append(AnyElement(**dict(gchild.attrib))) | ||
49 | else: | 55 | else: |
50 | pass | 56 | pass |
51 | return Sequence(sequence) | 57 | return Sequence(sequence) |
52 | 58 | ||
59 | +def get_extension(element): | ||
60 | + from .element import get_attributes | ||
61 | + for child in element: | ||
62 | + if isinstance(child, etree._Comment): | ||
63 | + continue | ||
64 | + if child.tag.endswith("}complexContent"): | ||
65 | + # extension | ||
66 | + if child[0].tag.endswith("}extension"): | ||
67 | + base_type = child[0].attrib["base"] | ||
68 | + sequence = get_sequence(child[0]) | ||
69 | + return (base_type, sequence) | ||
70 | + if child.tag.endswith("}simpleContent"): | ||
71 | + # extension | ||
72 | + if child[0].tag.endswith("}extension"): | ||
73 | + base_type = child[0].attrib["base"].split(":")[-1] | ||
74 | + attributes = get_attributes(child[0]) | ||
75 | + return (base_type, attributes) | ||
53 | 76 | ||
77 | + return None |
xsd2tkform/factory.py
@@ -2,25 +2,41 @@ | @@ -2,25 +2,41 @@ | ||
2 | """ | 2 | """ |
3 | import tkinter as tk | 3 | import tkinter as tk |
4 | 4 | ||
5 | -from xsd2tkform.form import XSDForm | ||
6 | -from xsd2tkform.core.parser import XSDParser | ||
7 | -from xsd2tkform.core.type import SimpleType, ComplexType | 5 | +from .form import XSDForm |
6 | +from .core.parser import XSDParser | ||
7 | +from .core.type import SimpleType, ComplexType | ||
8 | 8 | ||
9 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | ||
10 | -from xsd2tkform.ui.complextype import XSDComplexTypeFrame | ||
11 | -from xsd2tkform.ui.scrollframe import ScrollFrame | 9 | +from .ui.simpletype import XSDSimpleTypeFrame |
10 | +from .ui.complextype import XSDComplexTypeFrame | ||
11 | +from .ui.scrollframe import ScrollFrame | ||
12 | class XSDFormFactory: | 12 | class XSDFormFactory: |
13 | - def __init__(self, parser=None): | 13 | + def __init__(self, parser=None, widget_config={}): |
14 | + self.widget_config = widget_config | ||
14 | self.parser = parser | 15 | self.parser = parser |
15 | - | ||
16 | - def get_form(self, typename, parent): | 16 | + def get_schema_form(self, parent): |
17 | + # get form for schema | ||
18 | + schema = self.parser.schemas[0] | ||
19 | + root_frame = tk.Frame(parent) | ||
20 | + for item in schema.root_items: | ||
21 | + item_form = self.get_form(item.name, parent=root_frame) | ||
22 | + item_form.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
23 | + return root_frame | ||
24 | + def get_form(self, typename, parent, filename=None): | ||
17 | if not typename in self.parser.schemas[0]: | 25 | if not typename in self.parser.schemas[0]: |
18 | - raise Exception("Type {} not found") | 26 | + raise Exception("Type '{}' not found".format(typename)) |
19 | typedef = self.parser.get(typename) | 27 | typedef = self.parser.get(typename) |
20 | if isinstance(typedef, SimpleType): | 28 | if isinstance(typedef, SimpleType): |
21 | - return XSDSimpleTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False) | 29 | + return XSDSimpleTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config) |
22 | if isinstance(typedef, ComplexType): | 30 | if isinstance(typedef, ComplexType): |
23 | - return XSDComplexTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False) | 31 | + return XSDComplexTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config) |
32 | + from .core.element import Element | ||
33 | + if isinstance(typedef, Element): | ||
34 | + if typedef.typedef is None: | ||
35 | + return self.get_form(typedef.type, parent=parent, filename=filename) | ||
36 | + if isinstance(typedef.typedef, ComplexType): | ||
37 | + return XSDComplexTypeFrame(typedef.name, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config, typedef=typedef) | ||
38 | + if isinstance(typedef.typedef, SimpleType): | ||
39 | + return XSDSimpleTypeFrame(typedef.name, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config, typedef=typedef.typedef) | ||
24 | 40 | ||
25 | 41 | ||
26 | # example app | 42 | # example app |
@@ -42,28 +58,46 @@ if __name__=="__main__": | @@ -42,28 +58,46 @@ if __name__=="__main__": | ||
42 | parser = XSDParser(xsd_filename) | 58 | parser = XSDParser(xsd_filename) |
43 | # list complex types | 59 | # list complex types |
44 | typenames = [k for k in parser.schemas[0].complex_types] | 60 | typenames = [k for k in parser.schemas[0].complex_types] |
61 | + print("Simple types : \n", [k for k in parser.schemas[0].simple_types]) | ||
62 | + print("Complex types : \n", [k for k in parser.schemas[0].complex_types]) | ||
63 | + print("Elements : \n", [e.type for e in parser.schemas[0].items]) | ||
64 | + print("Root elements : \n", parser.schemas[0].root_items) | ||
45 | print("Creating form for type : {}".format(typenames[0])) | 65 | print("Creating form for type : {}".format(typenames[0])) |
46 | - print(typenames) | ||
47 | - | 66 | + print() |
67 | + for e in parser.schemas[0].items: | ||
68 | + if e.type=="get": | ||
69 | + print(e) | ||
70 | + print() | ||
71 | + print(parser.schemas[0].complex_types["GetterType"]) | ||
72 | + #exit() | ||
48 | 73 | ||
49 | # form factory | 74 | # form factory |
50 | factory=XSDFormFactory(parser) | 75 | factory=XSDFormFactory(parser) |
51 | 76 | ||
77 | + | ||
52 | app = ExampleApp() | 78 | app = ExampleApp() |
53 | mainframe = tk.Frame(app) | 79 | mainframe = tk.Frame(app) |
54 | scrollframe = ScrollFrame(mainframe) # add a new scrollable frame. | 80 | scrollframe = ScrollFrame(mainframe) # add a new scrollable frame. |
55 | 81 | ||
56 | # populate window with all simpleTypes found in the XSD schema | 82 | # populate window with all simpleTypes found in the XSD schema |
83 | + #form_frame = factory.get_schema_form(parent = scrollframe.viewPort) | ||
84 | + #form_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
85 | + | ||
86 | + tk.Label(scrollframe.viewPort, text="FILLED").pack(side=tk.TOP, fill=tk.X, expand=1) | ||
87 | + | ||
88 | + filled_form = factory.get_form("param", parent=scrollframe.viewPort, filename="ace_r.xml") | ||
89 | + filled_form.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
57 | #for t in typenames: | 90 | #for t in typenames: |
58 | - form_frame = factory.get_form(typename = typenames[0], parent = scrollframe.viewPort) | ||
59 | - form_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | 91 | + # #form_frame = factory.get_form(typename = t, parent = scrollframe.viewPort) |
92 | + # form_frame = factory.get_schema_form(parent = scrollframe.viewPort) | ||
93 | + # form_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
60 | 94 | ||
61 | # add a submit and cancel button | 95 | # add a submit and cancel button |
62 | def save_form(): | 96 | def save_form(): |
63 | - tree = etree.ElementTree(form_frame.get_content()) | 97 | + tree = etree.ElementTree(filled_form.get_content()) |
64 | from tkinter.filedialog import askopenfilename | 98 | from tkinter.filedialog import askopenfilename |
65 | filename = askopenfilename() | 99 | filename = askopenfilename() |
66 | - tree.write(filename, pretty_print=True) | 100 | + tree.write(filename, pretty_print=True, xml_declaration=True, encoding="utf-8") |
67 | submit_button = tk.Button(scrollframe.viewPort, text="Submit", command=save_form) | 101 | submit_button = tk.Button(scrollframe.viewPort, text="Submit", command=save_form) |
68 | cancel_button = tk.Button(scrollframe.viewPort, text="Cancel", command=app.quit) | 102 | cancel_button = tk.Button(scrollframe.viewPort, text="Cancel", command=app.quit) |
69 | submit_button.pack(side=tk.LEFT, fill=tk.X, expand=1) | 103 | submit_button.pack(side=tk.LEFT, fill=tk.X, expand=1) |
@@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
1 | +"""Text input widget with size adaptive height | ||
2 | +""" | ||
3 | + | ||
4 | +import tkinter as tk | ||
5 | +from tkinter import ttk | ||
6 | + | ||
7 | +class AdaptiveHeightText(tk.Text): | ||
8 | + def __init__(self, parent, height=None, width=None, *args, **kwargs): | ||
9 | + tk.Text.__init__(self, parent, height=height, width=width, *args, **kwargs) | ||
10 | + self.bind("<KeyRelease>", self.update_height) | ||
11 | + def update_height(self, event=None): | ||
12 | + text = self.get("1.0", tk.END) | ||
13 | + n_lines = len(text.split("\n"))+1 | ||
14 | + self.configure(height=n_lines) | ||
15 | + def insert(self, *args, **kwargs): | ||
16 | + super().insert(*args, **kwargs) | ||
17 | + self.update_height() | ||
18 | + def delete(self, *args, **kwargs): | ||
19 | + super().delete(*args, **kwargs) | ||
20 | + self.update_height() | ||
21 | + | ||
22 | +if __name__=="__main__": | ||
23 | + root = tk.Tk() | ||
24 | + mainframe = tk.Frame(root) | ||
25 | + | ||
26 | + text=AdaptiveHeightText(mainframe, height=2) | ||
27 | + text.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
28 | + | ||
29 | + text.insert("1.0", "a\nb\nc\n") | ||
30 | + | ||
31 | + mainframe.pack(side=tk.TOP, fill=tk.BOTH, expand=1) | ||
32 | + | ||
33 | + root.mainloop() | ||
34 | + |
@@ -0,0 +1,126 @@ | @@ -0,0 +1,126 @@ | ||
1 | +"""Frame for containing list of attributes | ||
2 | +""" | ||
3 | + | ||
4 | +import tkinter as tk | ||
5 | +from tkinter import ttk | ||
6 | + | ||
7 | +from .simpletype import XSDAttributeFrame | ||
8 | +from .entrywidgets import FloatEntry, IntEntry, BoolEntry | ||
9 | +from amda_xml_manager import collapse_image_file, expand_image_file | ||
10 | + | ||
11 | +class AttributeButtonFrame(tk.Frame): | ||
12 | + def __init__(self, parent): | ||
13 | + tk.Frame.__init__(self, parent) | ||
14 | + tk.Label(self, text="Add :", anchor=tk.W).grid(row=0, sticky=tk.W) | ||
15 | + def count_visible_children(self): | ||
16 | + c=0 | ||
17 | + for child in self.winfo_children(): | ||
18 | + ginfo=child.grid_info() | ||
19 | + if "row" in ginfo: | ||
20 | + c+=1 | ||
21 | + return c | ||
22 | + | ||
23 | +class AttributeFrame(tk.Frame): | ||
24 | + def __init__(self, parent, attributes=[]): | ||
25 | + tk.Frame.__init__(self, parent, highlightbackground="black", highlightthickness=1) | ||
26 | + | ||
27 | + #tk.Label(self, text="Attributes").grid(row=0, columnspan=2, sticky=tk.W) | ||
28 | + self.grid_columnconfigure(0, weight=0) | ||
29 | + self.grid_columnconfigure(1, weight=1) | ||
30 | + | ||
31 | + #images | ||
32 | + self.collapse_img = tk.PhotoImage(file=collapse_image_file) | ||
33 | + self.expand_img = tk.PhotoImage(file=expand_image_file) | ||
34 | + | ||
35 | + # add header | ||
36 | + self.get_header_frame() | ||
37 | + | ||
38 | + # content frame : content that is hidden when collapsed | ||
39 | + self.content_frame = tk.Frame(self) | ||
40 | + self.content_frame.grid_columnconfigure(0, weight=0) | ||
41 | + self.content_frame.grid_columnconfigure(1, weight=1) | ||
42 | + | ||
43 | + | ||
44 | + self.attributes=attributes | ||
45 | + # attribute counts | ||
46 | + self.counts={a.name:0 for a in self.attributes} | ||
47 | + | ||
48 | + # button container | ||
49 | + self.button_frame = AttributeButtonFrame(self.content_frame) | ||
50 | + | ||
51 | + self.attribute_inputs=[XSDAttributeFrame(a, parent=self.content_frame,\ | ||
52 | + on_delete=lambda t=a.name: self.delete_attribute(t),\ | ||
53 | + on_add=self.update_grid) for a in self.attributes] | ||
54 | + | ||
55 | + self.add_attribute_widgets() | ||
56 | + | ||
57 | + self.content_frame.grid(row=1, columnspan=2, sticky=tk.EW) | ||
58 | + def set_attribute_content(self, name, value): | ||
59 | + if "}" in name: | ||
60 | + name = name.split("}")[-1] | ||
61 | + print("attrib Setting {} : {}".format(name, value)) | ||
62 | + for i in range(len(self.attributes)): | ||
63 | + if name==self.attributes[i].name: | ||
64 | + A=self.attribute_inputs[i] | ||
65 | + A.set(value) | ||
66 | + | ||
67 | + ginfo=self.attribute_inputs[i].grid_info() | ||
68 | + if not "row" in ginfo: | ||
69 | + self.attribute_inputs[i].grid() | ||
70 | + def get_attribute_values(self): | ||
71 | + r={} | ||
72 | + for inp in self.attribute_inputs: | ||
73 | + if inp.is_visible(): | ||
74 | + v=inp.get() | ||
75 | + if len(v): | ||
76 | + r[inp.name]=v | ||
77 | + return r | ||
78 | + def add_attribute_widgets(self): | ||
79 | + # add button for each non required attributes | ||
80 | + row,col=0,1 | ||
81 | + for att in self.attribute_inputs: | ||
82 | + att.grid(row=row, column=1, sticky=tk.EW) | ||
83 | + l=att.get_label(self.content_frame) | ||
84 | + l.grid(row=row, column=0, sticky=tk.W) | ||
85 | + if not att.mandatory: | ||
86 | + att.grid_remove() | ||
87 | + l.grid_remove() | ||
88 | + | ||
89 | + b=att.get_add_button(parent=self.button_frame) | ||
90 | + b.grid(row=0, column=col) | ||
91 | + row+=1 | ||
92 | + col+=1 | ||
93 | + | ||
94 | + def get_header_frame(self): | ||
95 | + self.header_frame=tk.Frame(self) | ||
96 | + self.header_label=tk.Label(self.header_frame, text="Attributes", anchor=tk.W) | ||
97 | + self.header_label.pack(side=tk.LEFT, fill=tk.X, expand=1) | ||
98 | + self.collapse_button=tk.Button(self.header_frame, image=self.collapse_img, command=self.collapse) | ||
99 | + self.collapse_button.pack(side=tk.RIGHT, expand=0) | ||
100 | + | ||
101 | + self.header_frame.grid(row=0, columnspan=2, sticky=tk.EW) | ||
102 | + def collapse(self): | ||
103 | + self.content_frame.grid_remove() | ||
104 | + #for c in self.winfo_children(): | ||
105 | + # c.grid_remove() | ||
106 | + #self.header_frame.grid() | ||
107 | + self.collapse_button.configure(image=self.expand_img, command=self.expand) | ||
108 | + def expand(self): | ||
109 | + self.content_frame.grid() | ||
110 | + self.collapse_button.configure(image=self.collapse_img, command=self.collapse) | ||
111 | + def delete_attribute(self, name): | ||
112 | + self.counts[name]-=1 | ||
113 | + #self.update_grid() | ||
114 | + def update_grid(self): | ||
115 | + row=1 | ||
116 | + for f in self.attribute_inputs: | ||
117 | + row+=1 | ||
118 | + ttk.Separator(self, orient="horizontal").grid(row=row, columnspan=2, sticky=tk.EW) | ||
119 | + row+=1 | ||
120 | + if self.button_frame.count_visible_children()>1: | ||
121 | + self.button_frame.grid(row=row, columnspan=2, sticky=tk.EW) | ||
122 | + else: | ||
123 | + self.button_frame.grid_remove() | ||
124 | + | ||
125 | + def delete_attribute(self, name): | ||
126 | + self.update_grid() |
@@ -0,0 +1,152 @@ | @@ -0,0 +1,152 @@ | ||
1 | +""" | ||
2 | +tkentrycomplete.py | ||
3 | + | ||
4 | +A Tkinter widget that features autocompletion. | ||
5 | + | ||
6 | +Created by Mitja Martini on 2008-11-29. | ||
7 | +Updated by Russell Adams, 2011/01/24 to support Python 3 and Combobox. | ||
8 | +Updated by Dominic Kexel to use Tkinter and ttk instead of tkinter and tkinter.ttk | ||
9 | + Licensed same as original (not specified?), or public domain, whichever is less restrictive. | ||
10 | + | ||
11 | +""" | ||
12 | +import sys | ||
13 | +import os | ||
14 | +import tkinter | ||
15 | +from tkinter import ttk | ||
16 | + | ||
17 | +__version__ = "1.1" | ||
18 | + | ||
19 | +# I may have broken the unicode... | ||
20 | +tkinter_umlauts=['odiaeresis', 'adiaeresis', 'udiaeresis', 'Odiaeresis', 'Adiaeresis', 'Udiaeresis', 'ssharp'] | ||
21 | + | ||
22 | +class AutocompleteEntry(tkinter.Entry): | ||
23 | + """ | ||
24 | + Subclass of Tkinter.Entry that features autocompletion. | ||
25 | + | ||
26 | + To enable autocompletion use set_completion_list(list) to define | ||
27 | + a list of possible strings to hit. | ||
28 | + To cycle through hits use down and up arrow keys. | ||
29 | + """ | ||
30 | + def set_completion_list(self, completion_list): | ||
31 | + self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list | ||
32 | + self._hits = [] | ||
33 | + self._hit_index = 0 | ||
34 | + self.position = 0 | ||
35 | + self.bind('<KeyRelease>', self.handle_keyrelease) | ||
36 | + | ||
37 | + def autocomplete(self, delta=0): | ||
38 | + """autocomplete the Entry, delta may be 0/1/-1 to cycle through possible hits""" | ||
39 | + if delta: # need to delete selection otherwise we would fix the current position | ||
40 | + self.delete(self.position, tkinter.END) | ||
41 | + else: # set position to end so selection starts where textentry ended | ||
42 | + self.position = len(self.get()) | ||
43 | + # collect hits | ||
44 | + _hits = [] | ||
45 | + for element in self._completion_list: | ||
46 | + if element.lower().startswith(self.get().lower()): # Match case-insensitively | ||
47 | + _hits.append(element) | ||
48 | + # if we have a new hit list, keep this in mind | ||
49 | + if _hits != self._hits: | ||
50 | + self._hit_index = 0 | ||
51 | + self._hits=_hits | ||
52 | + # only allow cycling if we are in a known hit list | ||
53 | + if _hits == self._hits and self._hits: | ||
54 | + self._hit_index = (self._hit_index + delta) % len(self._hits) | ||
55 | + # now finally perform the auto completion | ||
56 | + if self._hits: | ||
57 | + self.delete(0,tkinter.END) | ||
58 | + self.insert(0,self._hits[self._hit_index]) | ||
59 | + self.select_range(self.position,tkinter.END) | ||
60 | + | ||
61 | + def handle_keyrelease(self, event): | ||
62 | + """event handler for the keyrelease event on this widget""" | ||
63 | + if event.keysym == "BackSpace": | ||
64 | + self.delete(self.index(tkinter.INSERT), tkinter.END) | ||
65 | + self.position = self.index(tkinter.END) | ||
66 | + if event.keysym == "Left": | ||
67 | + if self.position < self.index(tkinter.END): # delete the selection | ||
68 | + self.delete(self.position, tkinter.END) | ||
69 | + else: | ||
70 | + self.position = self.position-1 # delete one character | ||
71 | + self.delete(self.position, tkinter.END) | ||
72 | + if event.keysym == "Right": | ||
73 | + self.position = self.index(tkinter.END) # go to end (no selection) | ||
74 | + if event.keysym == "Down": | ||
75 | + self.autocomplete(1) # cycle to next hit | ||
76 | + if event.keysym == "Up": | ||
77 | + self.autocomplete(-1) # cycle to previous hit | ||
78 | + if len(event.keysym) == 1 or event.keysym in tkinter_umlauts: | ||
79 | + self.autocomplete() | ||
80 | + | ||
81 | +class AutocompleteCombobox(ttk.Combobox): | ||
82 | + | ||
83 | + def set_completion_list(self, completion_list): | ||
84 | + """Use our completion list as our drop down selection menu, arrows move through menu.""" | ||
85 | + self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list | ||
86 | + self._hits = [] | ||
87 | + self._hit_index = 0 | ||
88 | + self.position = 0 | ||
89 | + self.bind('<KeyRelease>', self.handle_keyrelease) | ||
90 | + self['values'] = self._completion_list # Setup our popup menu | ||
91 | + | ||
92 | + def autocomplete(self, delta=0): | ||
93 | + """autocomplete the Combobox, delta may be 0/1/-1 to cycle through possible hits""" | ||
94 | + if delta: # need to delete selection otherwise we would fix the current position | ||
95 | + self.delete(self.position, tkinter.END) | ||
96 | + else: # set position to end so selection starts where textentry ended | ||
97 | + self.position = len(self.get()) | ||
98 | + # collect hits | ||
99 | + _hits = [] | ||
100 | + for element in self._completion_list: | ||
101 | + if element.lower().startswith(self.get().lower()): # Match case insensitively | ||
102 | + _hits.append(element) | ||
103 | + # if we have a new hit list, keep this in mind | ||
104 | + if _hits != self._hits: | ||
105 | + self._hit_index = 0 | ||
106 | + self._hits=_hits | ||
107 | + # only allow cycling if we are in a known hit list | ||
108 | + if _hits == self._hits and self._hits: | ||
109 | + self._hit_index = (self._hit_index + delta) % len(self._hits) | ||
110 | + # now finally perform the auto completion | ||
111 | + if self._hits: | ||
112 | + self.delete(0,tkinter.END) | ||
113 | + self.insert(0,self._hits[self._hit_index]) | ||
114 | + self.select_range(self.position,tkinter.END) | ||
115 | + | ||
116 | + def handle_keyrelease(self, event): | ||
117 | + """event handler for the keyrelease event on this widget""" | ||
118 | + if event.keysym == "BackSpace": | ||
119 | + self.delete(self.index(tkinter.INSERT), tkinter.END) | ||
120 | + self.position = self.index(tkinter.END) | ||
121 | + if event.keysym == "Left": | ||
122 | + if self.position < self.index(tkinter.END): # delete the selection | ||
123 | + self.delete(self.position, tkinter.END) | ||
124 | + else: | ||
125 | + self.position = self.position-1 # delete one character | ||
126 | + self.delete(self.position, tkinter.END) | ||
127 | + if event.keysym == "Right": | ||
128 | + self.position = self.index(tkinter.END) # go to end (no selection) | ||
129 | + if len(event.keysym) == 1: | ||
130 | + self.autocomplete() | ||
131 | + # No need for up/down, we'll jump to the popup | ||
132 | + # list at the position of the autocompletion | ||
133 | + | ||
134 | +def test(test_list): | ||
135 | + """Run a mini application to test the AutocompleteEntry Widget.""" | ||
136 | + root = tkinter.Tk(className=' AutocompleteEntry demo') | ||
137 | + entry = AutocompleteEntry(root) | ||
138 | + entry.set_completion_list(test_list) | ||
139 | + entry.pack() | ||
140 | + entry.focus_set() | ||
141 | + combo = AutocompleteCombobox(root) | ||
142 | + combo.set_completion_list(test_list) | ||
143 | + combo.pack() | ||
144 | + combo.focus_set() | ||
145 | + # I used a tiling WM with no controls, added a shortcut to quit | ||
146 | + root.bind('<Control-Q>', lambda event=None: root.destroy()) | ||
147 | + root.bind('<Control-q>', lambda event=None: root.destroy()) | ||
148 | + root.mainloop() | ||
149 | + | ||
150 | +if __name__ == '__main__': | ||
151 | + test_list = ('apple', 'banana', 'CranBerry', 'dogwood', 'alpha', 'Acorn', 'Anise' ) | ||
152 | + test(test_list) |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +"""Frame for containing optional field buttons | ||
2 | +""" | ||
3 | +import tkinter as tk | ||
4 | + | ||
5 | +class ButtonContainer(tk.Frame): | ||
6 | + def __init__(self, parent, *args, **kwargs): | ||
7 | + #tk.Frame.__init__(self, parent, highlightbackground="magenta", highlightthickness=3, *args, **kwargs) | ||
8 | + tk.Frame.__init__(self, parent, highlightbackground=None, highlightthickness=None, *args, **kwargs) | ||
9 | + | ||
10 | + self.buttons = [] | ||
11 | + | ||
12 | + self.bind("<Configure>", self.update_layout) | ||
13 | + def add_button(self, button): | ||
14 | + self.buttons.append(button) | ||
15 | + def update_layout(self, event): | ||
16 | + pass | ||
17 | + #print("Update layout {}".format(event)) | ||
18 | + | ||
19 | + | ||
20 | + |
xsd2tkform/ui/choice.py
1 | import tkinter as tk | 1 | import tkinter as tk |
2 | from tkinter import ttk | 2 | from tkinter import ttk |
3 | 3 | ||
4 | -from xsd2tkform.core.type import SimpleType, ComplexType | 4 | +from ..core.type import SimpleType, ComplexType |
5 | 5 | ||
6 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | ||
7 | -from xsd2tkform.ui.complextype import XSDComplexTypeFrame | 6 | +from .simpletype import XSDSimpleTypeFrame |
7 | +from .complextype import XSDComplexTypeFrame | ||
8 | 8 | ||
9 | -from xsd2tkform.ui.field import XSDInputField | 9 | +from .field import XSDInputField |
10 | 10 | ||
11 | class ChoiceInput(XSDInputField): | 11 | class ChoiceInput(XSDInputField): |
12 | def __init__(self, parent, choice, schema, *args, **kwargs): | 12 | def __init__(self, parent, choice, schema, *args, **kwargs): |
13 | - tk.Frame.__init__(self, parent, *args, **kwargs) | 13 | + XSDInputField.__init__(self, parent, highlightbackground="green", highlightthickness=2, *args, **kwargs) |
14 | self.schema=schema | 14 | self.schema=schema |
15 | # get the choice types | 15 | # get the choice types |
16 | self.choice_inputs=[] | 16 | self.choice_inputs=[] |
17 | # get the list of choice type | 17 | # get the list of choice type |
18 | - choice_types = [t.type for t in choice.elements] | 18 | + self.choice_types = [t.type for t in choice.elements] |
19 | + self.choice_names = [t.name for t in choice.elements] | ||
19 | # get occurence bounds for choices | 20 | # get occurence bounds for choices |
20 | #choice_occ_bounds = self.get_element_occurence_limits(item) | 21 | #choice_occ_bounds = self.get_element_occurence_limits(item) |
21 | choice_occ_bounds = choice.min_occurs, choice.max_occurs | 22 | choice_occ_bounds = choice.min_occurs, choice.max_occurs |
@@ -23,7 +24,7 @@ class ChoiceInput(XSDInputField): | @@ -23,7 +24,7 @@ class ChoiceInput(XSDInputField): | ||
23 | self.field_counts={} | 24 | self.field_counts={} |
24 | self.field_min_counts={} | 25 | self.field_min_counts={} |
25 | self.field_max_counts={} | 26 | self.field_max_counts={} |
26 | - for _type in choice_types: | 27 | + for _type in self.choice_types: |
27 | # TODO : check if bounds are correct, if choice type is present somewhere else in the type definition then the bounds are overwritten here, which can be bad | 28 | # TODO : check if bounds are correct, if choice type is present somewhere else in the type definition then the bounds are overwritten here, which can be bad |
28 | if _type in self.field_min_counts: | 29 | if _type in self.field_min_counts: |
29 | print("Not good if you see this") | 30 | print("Not good if you see this") |
@@ -38,9 +39,13 @@ class ChoiceInput(XSDInputField): | @@ -38,9 +39,13 @@ class ChoiceInput(XSDInputField): | ||
38 | choice_select_frame = tk.Frame(self) | 39 | choice_select_frame = tk.Frame(self) |
39 | 40 | ||
40 | # add a choice selector : combobox and button | 41 | # add a choice selector : combobox and button |
41 | - self.choice_type = ttk.Combobox(choice_select_frame, values=choice_types, state="readonly") | 42 | + #self.choice_type = ttk.Combobox(choice_select_frame, values=self.choice_types, state="readonly") |
43 | + self.choice_type = ttk.Combobox(choice_select_frame, values=self.choice_names, state="readonly") | ||
44 | + | ||
42 | self.choice_type.current(0) | 45 | self.choice_type.current(0) |
43 | - choice_add = tk.Button(choice_select_frame, text="Add", command=lambda: self.add_field()) | 46 | + from amda_xml_manager import add_image_file |
47 | + self.add_img = tk.PhotoImage(file=add_image_file) | ||
48 | + choice_add = tk.Button(choice_select_frame, image=self.add_img, command=lambda: self.add_field()) | ||
44 | 49 | ||
45 | self.choice_type.pack(side=tk.LEFT, fill=tk.X, expand=1) | 50 | self.choice_type.pack(side=tk.LEFT, fill=tk.X, expand=1) |
46 | choice_add.pack(side=tk.RIGHT, expand=0) | 51 | choice_add.pack(side=tk.RIGHT, expand=0) |
@@ -48,16 +53,40 @@ class ChoiceInput(XSDInputField): | @@ -48,16 +53,40 @@ class ChoiceInput(XSDInputField): | ||
48 | ##choice_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | 53 | ##choice_frame.pack(side=tk.TOP, fill=tk.X, expand=1) |
49 | #sequence_inputs.append(choice_inputs) | 54 | #sequence_inputs.append(choice_inputs) |
50 | #return choice_frame | 55 | #return choice_frame |
51 | - def add_field(self): | ||
52 | - _type = self.choice_type.get() | 56 | + def has_type(self, t): |
57 | + # TODO watch out | ||
58 | + if t in self.choice_names: | ||
59 | + return True | ||
60 | + for _type in self.choice_types: | ||
61 | + if t==_type or t==_type.split(":")[-1]: | ||
62 | + return True | ||
63 | + return False | ||
64 | + def set_content(self, content, update_grid=True): | ||
65 | + # get the name of the item | ||
66 | + ct=content.tag.split("}")[-1] | ||
67 | + | ||
68 | + for c in self.choice_names: | ||
69 | + #for c in self.choice_types: | ||
70 | + ctt=c.split(":")[-1] | ||
71 | + if ct==ctt: | ||
72 | + self.choice_type.set(c) | ||
73 | + self.add_field(content) | ||
74 | + | ||
75 | + #self.add_field(content) | ||
76 | + | ||
77 | + def add_field(self, content=None): | ||
78 | + _name = self.choice_type.get() | ||
79 | + #_type = self.choice_type.get() | ||
80 | + _type = self.choice_types[self.choice_names.index(_name)] | ||
53 | 81 | ||
54 | # add the frame | 82 | # add the frame |
55 | - self.add_frame_by_type(_type) | ||
56 | - def add_frame_by_type(self, t): | 83 | + self.add_frame_by_type(_type, content=content) |
84 | + def add_frame_by_type(self, t, content=None): | ||
57 | # check if the maximum occurences of this field is achieved | 85 | # check if the maximum occurences of this field is achieved |
58 | 86 | ||
59 | if self.field_max_counts[t]=="unbounded": | 87 | if self.field_max_counts[t]=="unbounded": |
60 | - print("No limit on {} fields".format(t)) | 88 | + pass |
89 | + #print("No limit on {} fields".format(t)) | ||
61 | elif t in self.field_counts: | 90 | elif t in self.field_counts: |
62 | if self.get_field_count_by_type(t)==self.field_max_counts[t]: | 91 | if self.get_field_count_by_type(t)==self.field_max_counts[t]: |
63 | showerror(title="{} maximum occurences reached".format(t), message="Type {} supports a maximum of {} occurences of type {}".format(self.type, self.field_max_counts[t], t)) | 92 | showerror(title="{} maximum occurences reached".format(t), message="Type {} supports a maximum of {} occurences of type {}".format(self.type, self.field_max_counts[t], t)) |
@@ -67,27 +96,38 @@ class ChoiceInput(XSDInputField): | @@ -67,27 +96,38 @@ class ChoiceInput(XSDInputField): | ||
67 | 96 | ||
68 | self.increment_field_count_by_type(t) | 97 | self.increment_field_count_by_type(t) |
69 | 98 | ||
70 | - f = self.get_frame_by_type(t, parent=self, delete_button=True) | 99 | + f = self.get_frame_by_type(t, parent=self, delete_button=True, content=content) |
100 | + f.configure(highlightbackground="blue", highlightthickness=2) | ||
71 | f.pack(side=tk.TOP, fill=tk.X, expand=1) | 101 | f.pack(side=tk.TOP, fill=tk.X, expand=1) |
72 | - #self.choice_inputs.append(f.winfo_children()[0]) | ||
73 | - print("in choice_input append : {}".format(type(f))) | ||
74 | self.choice_inputs.append(f) | 102 | self.choice_inputs.append(f) |
75 | #if container is not None: | 103 | #if container is not None: |
76 | # container.append(f.winfo_children()[0]) | 104 | # container.append(f.winfo_children()[0]) |
77 | - def get_frame_by_type(self, t, parent=None, delete_button=False): | 105 | + #self.master.update_grid() |
106 | + | ||
107 | + def get_frame_by_type(self, t, parent=None, delete_button=False, content=None): | ||
78 | if parent is None: | 108 | if parent is None: |
79 | parent = self | 109 | parent = self |
80 | 110 | ||
81 | td=self.schema.get(t) | 111 | td=self.schema.get(t) |
112 | + nn=self.choice_names[self.choice_types.index(t)] | ||
113 | + print("IN CHOICE type={}, name={}".format(t, nn)) | ||
82 | if isinstance(td, SimpleType):# in self.simple_types: | 114 | if isinstance(td, SimpleType):# in self.simple_types: |
83 | return XSDSimpleTypeFrame(t, parent=parent,\ | 115 | return XSDSimpleTypeFrame(t, parent=parent,\ |
116 | + name=nn, | ||
84 | schema=self.schema, | 117 | schema=self.schema, |
85 | - delete_button=delete_button) | 118 | + delete_button=delete_button, |
119 | + content=content, | ||
120 | + widget_config=self.widget_config, | ||
121 | + on_delete=lambda x=t: self.delete_field(x)) | ||
86 | elif isinstance(td, ComplexType): | 122 | elif isinstance(td, ComplexType): |
87 | return XSDComplexTypeFrame(t, parent=parent,\ | 123 | return XSDComplexTypeFrame(t, parent=parent,\ |
124 | + name=nn, | ||
88 | schema=self.schema, | 125 | schema=self.schema, |
89 | delete_button=delete_button, | 126 | delete_button=delete_button, |
90 | - collapsable=True) | 127 | + collapsable=True, |
128 | + content=content, | ||
129 | + widget_config=self.widget_config, | ||
130 | + on_delete=lambda x=t: self.delete_field(x)) | ||
91 | else: | 131 | else: |
92 | # TODO : add Group support | 132 | # TODO : add Group support |
93 | print("Group support not yet implemented") | 133 | print("Group support not yet implemented") |
@@ -102,20 +142,10 @@ class ChoiceInput(XSDInputField): | @@ -102,20 +142,10 @@ class ChoiceInput(XSDInputField): | ||
102 | def decrement_field_count_by_type(self, t): | 142 | def decrement_field_count_by_type(self, t): |
103 | self.field_counts[t]=self.get_field_count_by_type(t)-1 | 143 | self.field_counts[t]=self.get_field_count_by_type(t)-1 |
104 | 144 | ||
105 | - def delete_field(self, t, field, container=None): | ||
106 | - field_dims = (field.winfo_width(), field.winfo_height()) | ||
107 | - field.destroy() | 145 | + def delete_field(self, t, field=None, container=None): |
108 | self.decrement_field_count_by_type(t) | 146 | self.decrement_field_count_by_type(t) |
109 | - current_scroll_region=self.master.master.bbox("all") | ||
110 | - new_scrollregion= (current_scroll_region[0], | ||
111 | - current_scroll_region[1], | ||
112 | - current_scroll_region[2]-field_dims[0], | ||
113 | - current_scroll_region[3]-field_dims[1]) | ||
114 | - | ||
115 | - # WORKS | ||
116 | - self.master.master.master.configure(scrollregion=new_scrollregion) | ||
117 | - | ||
118 | - | 147 | + self.choice_inputs=[w for w in self.choice_inputs if w.winfo_exists()] |
148 | + | ||
119 | def add_content(self, root, content): | 149 | def add_content(self, root, content): |
120 | if isinstance(content, list): | 150 | if isinstance(content, list): |
121 | for item in content: | 151 | for item in content: |
@@ -123,5 +153,6 @@ class ChoiceInput(XSDInputField): | @@ -123,5 +153,6 @@ class ChoiceInput(XSDInputField): | ||
123 | else: | 153 | else: |
124 | if content is not None: | 154 | if content is not None: |
125 | root.append(content) | 155 | root.append(content) |
126 | - def get_content(self): | ||
127 | - return [i.get_content(i) for i in self.choice_inputs] | 156 | + def get_content(self, nsmap=None, qname_attrs=None): |
157 | + return [i.get_content(nsmap=nsmap, qname_attrs=qname_attrs) for i in self.choice_inputs] | ||
158 | + #return [i.get_content(i, nsmap=nsmap, qname_attrs=qname_attrs) for i in self.choice_inputs] |
xsd2tkform/ui/complextype.py
@@ -4,173 +4,488 @@ from tkinter.messagebox import showerror | @@ -4,173 +4,488 @@ from tkinter.messagebox import showerror | ||
4 | 4 | ||
5 | from lxml import etree | 5 | from lxml import etree |
6 | 6 | ||
7 | -from xsd2tkform.core.type import SimpleType, ComplexType | ||
8 | -from xsd2tkform.core.element import Element | ||
9 | -from xsd2tkform.core.choice import Choice | 7 | +from ..core.type import SimpleType, ComplexType |
8 | +from ..core.element import Element, AnyElement | ||
9 | +from ..core.choice import Choice | ||
10 | 10 | ||
11 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | ||
12 | -from xsd2tkform.ui.tooltip import ToolTip | 11 | +from .simpletype import XSDSimpleTypeFrame, XSDAttributeFrame, XSDAnyInputFrame |
12 | +from .tooltip import ToolTip | ||
13 | 13 | ||
14 | from .field import XSDInputField | 14 | from .field import XSDInputField |
15 | 15 | ||
16 | +from amda_xml_manager import add_image_file, delete_image_file, collapse_image_file, expand_image_file | ||
17 | + | ||
18 | +from .attributeframe import AttributeFrame | ||
19 | + | ||
16 | class XSDComplexTypeFrame(XSDInputField): | 20 | class XSDComplexTypeFrame(XSDInputField): |
17 | - def __init__(self, typename, parent=None, schema=None, delete_button=False, collapsable=False, *args, **kwargs): | 21 | + def __init__(self, typename, parent=None, schema=None, delete_button=False, collapsable=False, filename=None, content=None, name=None, widget_config={}, typedef=None, *args, **kwargs): |
18 | XSDInputField.__init__(self, parent, borderwidth=1,\ | 22 | XSDInputField.__init__(self, parent, borderwidth=1,\ |
19 | highlightbackground="black",\ | 23 | highlightbackground="black",\ |
20 | - highlightthickness=2,\ | 24 | + highlightthickness=1,\ |
25 | + widget_config=widget_config,\ | ||
21 | *args, **kwargs) | 26 | *args, **kwargs) |
27 | + # flag indicating that the fields content has been set | ||
28 | + self.content_has_been_set = False | ||
29 | + | ||
30 | + self.grid_columnconfigure(0, weight=1) | ||
31 | + #self.grid_columnconfigure(1, weight=1) | ||
32 | + | ||
33 | + # collapsed state | ||
34 | + self.collapsed = False | ||
35 | + | ||
22 | # store type of the current field | 36 | # store type of the current field |
37 | + self.name = name | ||
23 | self.type = typename | 38 | self.type = typename |
24 | self.schema = schema | 39 | self.schema = schema |
25 | - | 40 | + |
41 | + # keep reference to the label | ||
42 | + self.label=None | ||
26 | # store the number of fields of each type, and min and max | 43 | # store the number of fields of each type, and min and max |
27 | self.field_counts={} | 44 | self.field_counts={} |
28 | self.field_min_counts={} | 45 | self.field_min_counts={} |
29 | self.field_max_counts={} | 46 | self.field_max_counts={} |
30 | 47 | ||
31 | - # storage for optional frame | ||
32 | - self.subframes=[] | ||
33 | - | ||
34 | # frame for containing label, collapse and delete buttons | 48 | # frame for containing label, collapse and delete buttons |
35 | - lf = tk.Frame(self) | ||
36 | - self.label=tk.Label(lf, text=self.sanitize_type(typename), font="bold") | ||
37 | - self.label.pack(side=tk.LEFT, fill=tk.X, expand=1) | ||
38 | - | 49 | + self.header_frame = self.get_header_frame() |
39 | 50 | ||
51 | + self.delete_img = None | ||
40 | if delete_button: | 52 | if delete_button: |
41 | - tk.Button(lf, text="X", command=self.delete).pack(side=tk.RIGHT) | 53 | + #tk.Button(self.header_frame, text="X", command=self.delete).pack(side=tk.RIGHT) |
54 | + self.delete_img = tk.PhotoImage(file=delete_image_file) | ||
55 | + tk.Button(self.header_frame, image=self.delete_img, command=self.delete).pack(side=tk.RIGHT) | ||
56 | + | ||
57 | + | ||
42 | # add a collapse button | 58 | # add a collapse button |
59 | + self.expand_img = None | ||
60 | + self.collapse_img = None | ||
43 | self.collapse_button=None | 61 | self.collapse_button=None |
44 | if collapsable: | 62 | if collapsable: |
45 | - self.collapse_button = tk.Button(lf, text="_", command=self.collapse) | 63 | + self.expand_img = tk.PhotoImage(file=expand_image_file) |
64 | + self.collapse_img = tk.PhotoImage(file=collapse_image_file) | ||
65 | + #self.collapse_button = tk.Button(self.header_frame, text="_", command=self.collapse) | ||
66 | + self.collapse_button = tk.Button(self.header_frame, image=self.collapse_img, command=self.collapse) | ||
46 | self.collapse_button.pack(side=tk.RIGHT) | 67 | self.collapse_button.pack(side=tk.RIGHT) |
47 | 68 | ||
48 | - | ||
49 | - lf.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
50 | 69 | ||
51 | self.inputs=[] # list of inputs used for constructing the tree | 70 | self.inputs=[] # list of inputs used for constructing the tree |
52 | 71 | ||
72 | + # option button frame | ||
73 | + #self.option_button_frame = tk.Frame(self) | ||
74 | + from .buttoncontainer import ButtonContainer | ||
75 | + self.option_button_frame2 = ButtonContainer(self) | ||
76 | + | ||
77 | + | ||
78 | + | ||
79 | + #tk.Label(self.option_button_frame, text="Add :").grid(row=0, column=0) | ||
80 | + tk.Label(self.option_button_frame2, text="Add :").grid(row=0, column=0) | ||
81 | + | ||
82 | + | ||
53 | # get type definition | 83 | # get type definition |
54 | - self.typedef = schema.get(typename) | 84 | + if typedef is None: |
85 | + self.typedef = schema.get(typename) | ||
86 | + else: | ||
87 | + self.typedef = typedef | ||
88 | + #from ..core.element import Element | ||
89 | + if isinstance(typedef, Element): | ||
90 | + if typedef.typedef is not None: | ||
91 | + self.typedef = typedef.typedef | ||
55 | if self.typedef is None: | 92 | if self.typedef is None: |
56 | raise Exception("Typename {} not found".format(typename)) | 93 | raise Exception("Typename {} not found".format(typename)) |
94 | + | ||
95 | + # attribute container | ||
96 | + if len(self.typedef.attributes): | ||
97 | + self.attribute_frame = AttributeFrame(self, self.typedef.attributes) | ||
98 | + self.attribute_frame.collapse() | ||
99 | + else: | ||
100 | + self.attribute_frame = None | ||
101 | + # field container | ||
102 | + #self.field_frame = tk.Frame(self, highlightbackground="red", highlightthickness=2) | ||
103 | + self.field_frame = tk.Frame(self, highlightbackground=None, highlightthickness=None) | ||
104 | + self.field_frame.grid_columnconfigure(0, weight=0) | ||
105 | + self.field_frame.grid_columnconfigure(1, weight=1) | ||
106 | + | ||
107 | + | ||
108 | + | ||
57 | self.set_tooltip() | 109 | self.set_tooltip() |
58 | - sequence_inputs=[] | 110 | + |
111 | + # grid content | ||
112 | + #c=0 | ||
113 | + #self.next_row = 1 | ||
114 | + option_button_column=1 | ||
115 | + # attributes first | ||
116 | + | ||
59 | for item in self.typedef.sequence.items: | 117 | for item in self.typedef.sequence.items: |
118 | + #print("item type {} {}".format(type(item), type(item.typedef))) | ||
60 | if isinstance(item, Element): | 119 | if isinstance(item, Element): |
120 | + if item.ref is not None: | ||
121 | + # there can be only ONE | ||
122 | + refered_element = self.schema.find_ref(item.ref) | ||
123 | + print("Refered element : {}".format(refered_element)) | ||
124 | + if refered_element.abstract: | ||
125 | + # find substitutions | ||
126 | + substitutions = self.schema.find_substitutions(refered_element.name) | ||
127 | + print("Substitutions : {}".format(substitutions)) | ||
128 | + print("\tnames : {}".format([s.name for s in substitutions])) | ||
129 | + if len(substitutions)==1: | ||
130 | + item = substitutions[0] | ||
131 | + else: | ||
132 | + #create a choice item | ||
133 | + item = Choice() | ||
134 | + for s in substitutions: | ||
135 | + item.add(s) | ||
136 | + from .choice import ChoiceInput | ||
137 | + chh = ChoiceInput(self.field_frame, item, self.schema, widget_config=self.widget_config) | ||
138 | + #self.grid_contents.append(chh) | ||
139 | + self.inputs.append(chh) | ||
140 | + continue | ||
141 | + | ||
61 | self.set_occurrence_bounds(item) | 142 | self.set_occurrence_bounds(item) |
62 | - element_field = self.get_element_field(item, sequence_inputs) | ||
63 | - element_field.pack(side=tk.TOP, fill=tk.X, expand=1) | 143 | + |
144 | + for element_field in self.get_element_field(item, self.inputs): | ||
145 | + # add to grid contents | ||
146 | + #self.grid_contents.append(element_field) | ||
147 | + | ||
148 | + from .optional import OptionalInput | ||
149 | + if isinstance(element_field, OptionalInput): | ||
150 | + # pack the button at the bottom of the field | ||
151 | + but = element_field.get_add_button(parent = self.option_button_frame2) | ||
152 | + self.option_button_frame2.add_button(but) | ||
153 | + but.grid(row=0, column=option_button_column) | ||
154 | + option_button_column+=1 | ||
155 | + | ||
64 | elif isinstance(item, Choice): | 156 | elif isinstance(item, Choice): |
65 | - from xsd2tkform.ui.choice import ChoiceInput | ||
66 | - chh = ChoiceInput(self, item, self.schema) | ||
67 | - chh.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
68 | - sequence_inputs.append(chh) | 157 | + from .choice import ChoiceInput |
158 | + chh = ChoiceInput(self.field_frame, item, self.schema, widget_config=self.widget_config) | ||
159 | + #self.grid_contents.append(chh) | ||
160 | + self.inputs.append(chh) | ||
161 | + elif isinstance(item, AnyElement): | ||
162 | + from .adaptivetextentry import AdaptiveHeightText | ||
163 | + r=XSDAnyInputFrame(parent=self.field_frame,\ | ||
164 | + input_widget_type=lambda p: AdaptiveHeightText(p, height=3)) | ||
165 | + self.inputs.append(r) | ||
166 | + | ||
69 | else: | 167 | else: |
70 | # TODO : add groups | 168 | # TODO : add groups |
71 | print("Group support not implemeneted yet") | 169 | print("Group support not implemeneted yet") |
72 | - self.inputs.append(sequence_inputs) | ||
73 | - def collapse(self): | ||
74 | - # hide all inputs | ||
75 | - for i in self.inputs: | ||
76 | - if isinstance(i, list): | ||
77 | - for e in i: | ||
78 | - e.pack_forget() | 170 | + |
171 | + #c+=1 | ||
172 | + | ||
173 | + row=1 | ||
174 | + if self.attribute_frame is not None: | ||
175 | + self.attribute_frame.grid(row=1, sticky=tk.EW) | ||
176 | + row+=1 | ||
177 | + self.field_frame.grid(row=row, sticky=tk.EW) | ||
178 | + | ||
179 | + | ||
180 | + | ||
181 | + # if filename or content was given | ||
182 | + if filename is not None: | ||
183 | + print("Setting content from {}".format(filename)) | ||
184 | + self.set_content(filename=filename) | ||
185 | + if content is not None: | ||
186 | + self.set_content(content=content) | ||
187 | + | ||
188 | + def get_header_frame(self): | ||
189 | + hf=tk.Frame(self) | ||
190 | + if self.name is None: | ||
191 | + label_text=self.sanitize_type(self.type) | ||
192 | + else: | ||
193 | + label_text=self.name | ||
194 | + | ||
195 | + #self.header_label=tk.Label(hf, text=label_text, font="bold", anchor="w") | ||
196 | + self.header_label=tk.Label(hf, text=label_text, font=("Helvetica",11,"bold"), anchor="w") | ||
197 | + | ||
198 | + self.header_label.pack(side=tk.LEFT, fill=tk.X, expand=1) | ||
199 | + | ||
200 | + self.header_label.bind("<Button-1>", self.collapse) | ||
201 | + return hf | ||
202 | + | ||
203 | + | ||
204 | + def clear_grid(self): | ||
205 | + for child in self.field_frame.winfo_children(): | ||
206 | + # delete labels | ||
207 | + if isinstance(child, tk.Label): | ||
208 | + child.destroy() | ||
209 | + elif isinstance(child, ttk.Separator): | ||
210 | + child.destroy() | ||
79 | else: | 211 | else: |
80 | - i.pack_forget() | 212 | + child.grid_forget() |
213 | + def get_label(self, parent): | ||
214 | + print(" ------> ComplexType name={}, type={}".format(self.name, self.type)) | ||
215 | + label_text=self.sanitize_type(self.type) | ||
216 | + if isinstance(self.typedef, Element): | ||
217 | + label_text=self.typedef.name | ||
218 | + if self.name is not None: | ||
219 | + label_text=self.name | ||
220 | + | ||
221 | + self.label = tk.Label(parent, text=label_text+" :") | ||
222 | + self.set_tooltip() | ||
223 | + return self.label | ||
224 | + def get_fields(self): | ||
225 | + from .optional import OptionalInput | ||
226 | + l=[] | ||
227 | + #for item in self.grid_contents: | ||
228 | + for item in self.inputs: | ||
229 | + if isinstance(item, OptionalInput): | ||
230 | + l+=[w for w in item.get_fields()] | ||
231 | + else: | ||
232 | + l.append(item) | ||
233 | + return l | ||
234 | + def remove_separators(self): | ||
235 | + for child in self.field_frame.winfo_children(): | ||
236 | + if isinstance(child, ttk.Separator): | ||
237 | + child.destroy() | ||
238 | + def count_grid_contents(self): | ||
239 | + c={} | ||
240 | + for child in self.field_frame.winfo_children(): | ||
241 | + if type(child) in c: | ||
242 | + c[type(child)]+=1 | ||
243 | + else: | ||
244 | + c[type(child)]=1 | ||
245 | + for k in c: | ||
246 | + print("{} : {}".format(k, c[k])) | ||
247 | + | ||
248 | + def update_attribute_grid(self): | ||
249 | + if self.attribute_frame is not None: | ||
250 | + self.attribute_frame.update_grid() | ||
251 | + | ||
252 | + def update_grid(self): | ||
253 | + print("update_grid name={}, type={}, self.type={}".format(self.name, self.type, type(self))) | ||
254 | + from .optional import OptionalInput | ||
255 | + from .choice import ChoiceInput | ||
256 | + | ||
257 | + self.remove_separators() | ||
258 | + # attribute grid update | ||
259 | + self.update_attribute_grid() | ||
260 | + # get fields | ||
261 | + new_fields = self.get_fields() | ||
262 | + | ||
263 | + # add the contents of the grid | ||
264 | + self.header_frame.grid(row=0, columnspan=2, sticky=tk.EW) | ||
265 | + if not self.collapsed: | ||
266 | + #ttk.Separator(self, orient="horizontal").grid(row=1, columnspan=2, sticky=tk.EW) | ||
267 | + row=2 | ||
268 | + for f in new_fields: | ||
269 | + # add the input field | ||
270 | + grid_info = f.grid_info() | ||
271 | + if "row" in grid_info: | ||
272 | + if grid_info["row"]!=row: | ||
273 | + f.grid_forget() # remove the label too | ||
274 | + f.grid(row=row, column=1, sticky=tk.EW) | ||
275 | + else: | ||
276 | + f.grid(row=row, column=1, sticky=tk.EW) | ||
277 | + | ||
278 | + # check the label | ||
279 | + if not isinstance(f, ChoiceInput): | ||
280 | + if f.label is None: | ||
281 | + f.get_label(self.field_frame).grid(row=row, column=0, sticky=tk.NW) | ||
282 | + else: | ||
283 | + label_grid_info = f.label.grid_info() | ||
284 | + if "row" in label_grid_info: | ||
285 | + if label_grid_info["row"]!=row: | ||
286 | + f.label.grid_forget() | ||
287 | + f.label.grid(row=row, column=0, sticky=tk.NW) | ||
288 | + else: | ||
289 | + f.label.grid(row=row, column=0, sticky=tk.NW) | ||
290 | + row+=1 | ||
291 | + ttk.Separator(self.field_frame, orient="horizontal").grid(row=row, columnspan=2, sticky=tk.EW) | ||
292 | + row+=1 | ||
293 | + n=len(list(self.option_button_frame2.winfo_children())) | ||
294 | + if n>1: | ||
295 | + self.option_button_frame2.grid(row=row, columnspan=2, sticky=tk.EW) | ||
296 | + | ||
297 | + def grid(self, *args, **kwargs): | ||
298 | + self.update_grid() | ||
299 | + return super().grid(pady=1,*args, **kwargs) | ||
300 | + def pack(self, *args, **kwargs): | ||
301 | + self.update_grid() | ||
302 | + return super().pack(pady=1, *args, **kwargs) | ||
303 | + if isinstance(self.master, XSDComplexTypeFrame): | ||
304 | + #super().pack(*args, padx=(100,0), **kwargs) | ||
305 | + super().pack(*args, **kwargs) | ||
306 | + | ||
307 | + else: | ||
308 | + super().pack(*args, **kwargs) | ||
309 | + def iter_inputs(self): | ||
310 | + for e in self.inputs: | ||
311 | + if isinstance(e, list): | ||
312 | + for el in e: | ||
313 | + yield el | ||
314 | + else: | ||
315 | + yield e | ||
316 | + def set_content(self, filename=None, content=None, update_grid=True): | ||
317 | + if filename is not None: | ||
318 | + root = etree.parse(filename).getroot() | ||
319 | + return self.set_content(content=root, update_grid=update_grid) | ||
320 | + if content is not None: | ||
321 | + # set attribute content | ||
322 | + for att in content.attrib: | ||
323 | + self.attribute_frame.set_attribute_content(att, content.attrib[att]) | ||
324 | + # go over children of content | ||
325 | + for child in content: | ||
326 | + if isinstance(child, etree._Comment): | ||
327 | + continue | ||
328 | + self.set_child_content(child, update_grid=True) | ||
329 | + self.content_has_been_set=True | ||
330 | + if update_grid: | ||
331 | + self.update_grid() | ||
332 | + def is_full(self): | ||
333 | + # check if this field is full : all mandatory fields are filled | ||
334 | + return self.content_has_been_set | ||
335 | + def set_child_content(self, child, update_grid=True): | ||
336 | + ct=child.tag.split("}")[-1] | ||
337 | + ctype = self.schema.get(ct) | ||
338 | + from ..core.element import Element | ||
339 | + for minp in self.iter_inputs(): | ||
340 | + if isinstance(minp, XSDSimpleTypeFrame) or isinstance(minp, XSDComplexTypeFrame): | ||
341 | + if minp.name is None: | ||
342 | + ctt=minp.type.split(":")[-1] | ||
343 | + else: | ||
344 | + ctt=minp.name | ||
345 | + if ct==ctt: | ||
346 | + # if the field is already filled skip | ||
347 | + if minp.is_full(): | ||
348 | + continue | ||
349 | + minp.set_content(content=child, update_grid=True) | ||
350 | + if isinstance(minp, XSDComplexTypeFrame): | ||
351 | + minp.collapse() | ||
352 | + return | ||
353 | + from .optional import OptionalInput | ||
354 | + if isinstance(minp, OptionalInput): | ||
355 | + if minp.is_full(): | ||
356 | + continue | ||
357 | + ctt=minp.type.split(":")[-1] | ||
358 | + if ct==ctt: | ||
359 | + minp.set_content(content=child, update_grid=True) | ||
360 | + return | ||
361 | + from .choice import ChoiceInput | ||
362 | + if isinstance(minp, ChoiceInput): | ||
363 | + if minp.has_type(ct): | ||
364 | + minp.set_content(content=child, update_grid=True) | ||
365 | + return | ||
366 | + | ||
367 | + def collapse(self, event=None): | ||
368 | + self.collapsed = True | ||
369 | + if self.attribute_frame is not None: | ||
370 | + self.attribute_frame.grid_remove() | ||
371 | + for item in self.field_frame.winfo_children(): | ||
372 | + if isinstance(item, tk.Label): | ||
373 | + item.grid_remove() | ||
374 | + if isinstance(item, XSDInputField): | ||
375 | + item.grid_remove() | ||
376 | + # collapse the optional button frame | ||
377 | + self.option_button_frame2.grid_remove() | ||
378 | + | ||
81 | # change button action to expand | 379 | # change button action to expand |
82 | - self.collapse_button.configure(text="+", command=self.expand) | 380 | + self.collapse_button.configure(image=self.expand_img, command=self.expand) |
83 | # change the label text | 381 | # change the label text |
84 | in_val=[] | 382 | in_val=[] |
85 | for i in self.inputs: | 383 | for i in self.inputs: |
86 | - if isinstance(i, list): | ||
87 | - for e in i: | ||
88 | - if isinstance(e, XSDSimpleTypeFrame): | ||
89 | - in_val+=["{}:{}".format(self.sanitize_type(e.type),e.get_value())] | ||
90 | - new_lab = "{}({})".format(self.sanitize_type(self.type), | 384 | + if isinstance(i, XSDSimpleTypeFrame): |
385 | + tval = str(i.get_value()) | ||
386 | + if len(tval)>100: | ||
387 | + tval=tval[:30]+"..." | ||
388 | + in_val+=["{}:{}".format(self.sanitize_type(i.type),tval)] | ||
389 | + l=self.sanitize_type(self.type) | ||
390 | + if self.name is not None: | ||
391 | + l=self.name | ||
392 | + new_lab = "{}({})".format(l, | ||
91 | ",".join(in_val)) | 393 | ",".join(in_val)) |
92 | - self.label.configure(text=new_lab) | ||
93 | - def expand(self): | ||
94 | - # hide all inputs | ||
95 | - for i in self.inputs: | ||
96 | - if isinstance(i, list): | ||
97 | - for e in i: | ||
98 | - e.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
99 | - else: | ||
100 | - i.pack(side=tk.TOP, fill=tk.X, expand=1) | 394 | + w=int(self.winfo_width()*.8) |
395 | + self.header_label.configure(text=new_lab, wraplength=w, justify="left") | ||
396 | + self.header_label.bind("<Button-1>", self.expand) | ||
397 | + def expand(self, event=None): | ||
398 | + #self.update_grid() | ||
399 | + from .optional import OptionalInput | ||
400 | + self.collapsed = False | ||
401 | + if self.attribute_frame is not None: | ||
402 | + self.attribute_frame.grid() | ||
403 | + for item in self.field_frame.winfo_children(): | ||
404 | + if isinstance(item, tk.Label): | ||
405 | + item.grid() | ||
406 | + if isinstance(item, XSDInputField): | ||
407 | + if isinstance(item, OptionalInput): | ||
408 | + continue | ||
409 | + item.grid() | ||
410 | + | ||
411 | + | ||
412 | + # option button frame | ||
413 | + #self.option_button_frame.pack(side=tk.BOTTOM, fill=tk.X, expand=1) | ||
414 | + self.option_button_frame2.grid() | ||
415 | + | ||
101 | # change button action to collapse | 416 | # change button action to collapse |
102 | - self.collapse_button.configure(text="_", command=self.collapse) | 417 | + self.collapse_button.configure(image=self.collapse_img, command=self.collapse) |
103 | # set original lable | 418 | # set original lable |
104 | - self.label.configure(text=self.sanitize_type(self.type)) | 419 | + l=self.sanitize_type(self.type) |
420 | + if self.name is not None: | ||
421 | + l=self.name | ||
422 | + self.header_label.configure(text=l, justify="center") | ||
423 | + self.header_label.bind("<Button-1>", self.collapse) | ||
424 | + self.update_grid() | ||
425 | + | ||
105 | def delete(self): | 426 | def delete(self): |
106 | - self.master.decrement_field_count_by_type(self.type) | 427 | + #self.master.decrement_field_count_by_type(self.type) |
107 | self.destroy() | 428 | self.destroy() |
108 | - def get_choice_field(self, item, sequence_inputs): | ||
109 | - choice_inputs=[] | ||
110 | - # get the list of choice type | ||
111 | - choice_types = [t.type for t in item.elements] | ||
112 | - # get occurence bounds for choices | ||
113 | - #choice_occ_bounds = self.get_element_occurence_limits(item) | ||
114 | - choice_occ_bounds = item.min_occurs, item.max_occurs | ||
115 | - # set those bounds for all types | ||
116 | - for _type in choice_types: | ||
117 | - # TODO : check if bounds are correct, if choice type is present somewhere else in the type definition then the bounds are overwritten here, which can be bad | ||
118 | - if _type in self.field_min_counts: | ||
119 | - print("Not good if you see this") | ||
120 | - if _type in self.field_max_counts: | ||
121 | - print("Not good if you see this") | ||
122 | - self.field_min_counts[_type]=choice_occ_bounds[0] | ||
123 | - self.field_max_counts[_type]=choice_occ_bounds[1] | ||
124 | - # frame for storing the selector and choices | ||
125 | - choice_frame = tk.Frame(self) | ||
126 | - # create a frame to store the choice selector | ||
127 | - choice_select_frame = tk.Frame(choice_frame) | 429 | + if self.on_delete is not None: |
430 | + self.on_delete() | ||
431 | + if self.label is not None: | ||
432 | + self.label.destroy() | ||
128 | 433 | ||
129 | - # add a choice selector : combobox and button | ||
130 | - choice_type = ttk.Combobox(choice_select_frame, values=choice_types, state="readonly") | ||
131 | - choice_type.current(0) | ||
132 | - choice_add = tk.Button(choice_select_frame, text="Add", command=lambda w=choice_type, frame=choice_frame, container=choice_inputs: self.add_frame_by_type(w.get(), frame, container)) | ||
133 | - | ||
134 | - choice_type.pack(side=tk.LEFT, fill=tk.X, expand=1) | ||
135 | - choice_add.pack(side=tk.RIGHT, expand=0) | ||
136 | - choice_select_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
137 | - ##choice_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
138 | - sequence_inputs.append(choice_inputs) | ||
139 | - return choice_frame | ||
140 | 434 | ||
141 | def get_element_field(self, item, sequence_inputs): | 435 | def get_element_field(self, item, sequence_inputs): |
142 | - if item.min_occurs==0: | ||
143 | - # optional field, add a frame to contain the eventual fields, with a button | ||
144 | - b_frame= tk.Frame(self) | ||
145 | - b=tk.Button(b_frame, text="Add {}".format(item.type), command=lambda t=item.type, frame=b_frame, container=sequence_inputs: self.add_frame_by_type(t, frame, container)) | ||
146 | - # add tooltip | ||
147 | - doc_str = self.get_type_docstring(item.type) | ||
148 | - if len(doc_str): | ||
149 | - ttt = ToolTip(b, doc_str) | ||
150 | - b.pack(side=tk.TOP, fill=tk.X, expand=1) | 436 | + from .optional import OptionalInput |
437 | + if isinstance(item, AnyElement): | ||
438 | + of = self.get_frame_by_type(item) | ||
439 | + sequence_inputs.append(of) # store the field for later use | ||
440 | + yield of | ||
151 | 441 | ||
442 | + if item.min_occurs==0: | ||
152 | # temp | 443 | # temp |
153 | - from xsd2tkform.ui.optional import OptionalInput | ||
154 | bounds=(self.field_min_counts[item.type], self.field_max_counts[item.type]) | 444 | bounds=(self.field_min_counts[item.type], self.field_max_counts[item.type]) |
155 | - of=OptionalInput(self, item, self.schema, bounds=bounds) | ||
156 | - of.pack(side=tk.TOP, fill=tk.X, expand=1) | 445 | + of=OptionalInput(self.field_frame, item, self.schema, bounds=bounds, widget_config=self.widget_config, on_add_field=self.update_grid,\ |
446 | + on_delete_field=lambda t=item.type: self.decrement_field_count_by_type(t)\ | ||
447 | + ) | ||
448 | + | ||
157 | sequence_inputs.append(of) | 449 | sequence_inputs.append(of) |
158 | - return of | ||
159 | - return b_frame | 450 | + yield of |
160 | else: | 451 | else: |
161 | - # mandatory field | ||
162 | - f = self.get_frame_by_type(item.type) | ||
163 | - sequence_inputs.append(f) # store the field for later use | ||
164 | - return f | 452 | + # yield mandatory items |
453 | + for i in range(item.min_occurs): | ||
454 | + of = self.get_frame_by_type(item) | ||
455 | + if of is None: | ||
456 | + continue | ||
457 | + sequence_inputs.append(of) # store the field for later use | ||
458 | + yield of | ||
459 | + # get optional bounds | ||
460 | + bounds=[0, self.field_max_counts[item.type]] | ||
461 | + if bounds[1]!="unbounded": | ||
462 | + bounds[1]=bounds[1]-item.min_occurs | ||
463 | + a = (bounds[1]=="unbounded") | ||
464 | + b = False | ||
465 | + if not a: | ||
466 | + b = bounds[1] | ||
467 | + | ||
468 | + if a or b: | ||
469 | + # yield optional items | ||
470 | + of=OptionalInput(self.field_frame, item, self.schema, bounds=tuple(bounds), widget_config=self.widget_config, on_add_field=self.update_grid, \ | ||
471 | + on_delete_field=lambda t=item.type: self.decrement_field_count_by_type(t)\ | ||
472 | + ) | ||
473 | + sequence_inputs.append(of) | ||
474 | + yield of | ||
475 | + | ||
165 | 476 | ||
166 | def set_occurrence_bounds(self, item): | 477 | def set_occurrence_bounds(self, item): |
167 | self.field_min_counts[item.type]=item.min_occurs | 478 | self.field_min_counts[item.type]=item.min_occurs |
168 | self.field_max_counts[item.type]=item.max_occurs | 479 | self.field_max_counts[item.type]=item.max_occurs |
169 | 480 | ||
170 | def set_tooltip(self): | 481 | def set_tooltip(self): |
482 | + if self.label is None or self.typedef is None: | ||
483 | + return | ||
484 | + if self.typedef.annotation is None: | ||
485 | + return | ||
171 | if len(self.typedef.annotation.documentation): | 486 | if len(self.typedef.annotation.documentation): |
172 | langs = [k for k in self.typedef.annotation.documentation] | 487 | langs = [k for k in self.typedef.annotation.documentation] |
173 | - tt = ToolTip(self.label, self.typedef.annotation.documentation[langs[0]]) | 488 | + tt = ToolTip(self.header_label, self.typedef.annotation.documentation[langs[0]]) |
174 | 489 | ||
175 | def get_type_docstring(self, t): | 490 | def get_type_docstring(self, t): |
176 | td=self.schema.get(t) | 491 | td=self.schema.get(t) |
@@ -209,8 +524,6 @@ class XSDComplexTypeFrame(XSDInputField): | @@ -209,8 +524,6 @@ class XSDComplexTypeFrame(XSDInputField): | ||
209 | def increment_field_count_by_type(self, t): | 524 | def increment_field_count_by_type(self, t): |
210 | self.field_counts[t]=self.get_field_count_by_type(t)+1 | 525 | self.field_counts[t]=self.get_field_count_by_type(t)+1 |
211 | def decrement_field_count_by_type(self, t): | 526 | def decrement_field_count_by_type(self, t): |
212 | - print("Current {} field count {}".format(t, self.get_field_count_by_type(t))) | ||
213 | - print([k for k in self.field_counts]) | ||
214 | self.field_counts[t]=self.get_field_count_by_type(t)-1 | 527 | self.field_counts[t]=self.get_field_count_by_type(t)-1 |
215 | def delete_field(self, t, field, container=None): | 528 | def delete_field(self, t, field, container=None): |
216 | field_dims = (field.winfo_width(), field.winfo_height()) | 529 | field_dims = (field.winfo_width(), field.winfo_height()) |
@@ -226,47 +539,64 @@ class XSDComplexTypeFrame(XSDInputField): | @@ -226,47 +539,64 @@ class XSDComplexTypeFrame(XSDInputField): | ||
226 | self.master.master.configure(scrollregion=new_scrollregion) | 539 | self.master.master.configure(scrollregion=new_scrollregion) |
227 | 540 | ||
228 | 541 | ||
229 | - def add_frame_by_type(self, t, frame=None, container=None): | ||
230 | - print("in add frame by type : " , t) | ||
231 | - # check if the maximum occurences of this field is achieved | ||
232 | - | ||
233 | - if self.field_max_counts[t]=="unbounded": | ||
234 | - print("No limit on {} fields".format(t)) | ||
235 | - elif t in self.field_counts: | ||
236 | - if self.get_field_count_by_type(t)==self.field_max_counts[t]: | ||
237 | - showerror(title="{} maximum occurences reached".format(t), message="Type {} supports a maximum of {} occurences of type {}".format(self.type, self.field_max_counts[t], t)) | ||
238 | - return | ||
239 | - else: | ||
240 | - pass | ||
241 | - | ||
242 | - self.increment_field_count_by_type(t) | ||
243 | - | ||
244 | - if frame is None: | ||
245 | - f = self.get_frame_by_type(t, delete_button=True) | ||
246 | - f.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
247 | - else: | ||
248 | - f = self.get_frame_by_type(t, parent=frame, delete_button=True) | ||
249 | - f.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
250 | - if container is not None: | ||
251 | - container.append(f) | ||
252 | - #container.append(f.winfo_children()[0]) | ||
253 | def get_frame_by_type(self, t, parent=None, delete_button=False): | 542 | def get_frame_by_type(self, t, parent=None, delete_button=False): |
543 | + #from ..core.type import SimpleType | ||
254 | if parent is None: | 544 | if parent is None: |
255 | - parent = self | ||
256 | - | ||
257 | - td=self.schema.get(t) | 545 | + parent = self.field_frame |
546 | + if isinstance(t, AnyElement): | ||
547 | + from .adaptivetextentry import AdaptiveHeightText | ||
548 | + return XSDAnyInputFrame(parent=parent,\ | ||
549 | + input_widget=AdaptiveHeightText) | ||
550 | + | ||
551 | + | ||
552 | + if t.type is None: | ||
553 | + td=self.schema.get(t.name) | ||
554 | + else: | ||
555 | + td=self.schema.get(t.type) | ||
556 | + if td is None: | ||
557 | + # if type is native type | ||
558 | + if t.type.split(":")[-1]=="string": | ||
559 | + # return a SimpleType object | ||
560 | + from ..core.restriction import Restriction | ||
561 | + #from ..core.type import SimpleType | ||
562 | + td=SimpleType(t.name, restriction=Restriction(base="string")) | ||
563 | + return XSDSimpleTypeFrame(t.name, name=t.name, parent=parent,\ | ||
564 | + schema=self.schema, delete_button=delete_button,\ | ||
565 | + widget_config=self.widget_config,\ | ||
566 | + typedef=td) | ||
567 | + | ||
258 | if isinstance(td, SimpleType):# in self.simple_types: | 568 | if isinstance(td, SimpleType):# in self.simple_types: |
259 | - return XSDSimpleTypeFrame(t, parent=parent,\ | 569 | + return XSDSimpleTypeFrame(t.type, name=t.name, parent=parent,\ |
260 | schema=self.schema, | 570 | schema=self.schema, |
261 | - delete_button=delete_button) | 571 | + delete_button=delete_button, |
572 | + widget_config=self.widget_config) | ||
262 | elif isinstance(td, ComplexType): | 573 | elif isinstance(td, ComplexType): |
263 | - return XSDComplexTypeFrame(t, parent=parent,\ | 574 | + return XSDComplexTypeFrame(t.type, name=t.name, parent=parent,\ |
264 | schema=self.schema, | 575 | schema=self.schema, |
265 | delete_button=delete_button, | 576 | delete_button=delete_button, |
266 | - collapsable=True) | 577 | + collapsable=True, |
578 | + widget_config=self.widget_config) | ||
267 | else: | 579 | else: |
268 | # TODO : add Group support | 580 | # TODO : add Group support |
269 | print("Group support not yet implemented") | 581 | print("Group support not yet implemented") |
582 | + #if td.typedef is None: | ||
583 | + # return None | ||
584 | + if td.ref is not None: | ||
585 | + return None | ||
586 | + if isinstance(td.typedef, SimpleType): | ||
587 | + return XSDSimpleTypeFrame(t.type, name=t.name, parent=parent,\ | ||
588 | + schema=self.schema, | ||
589 | + delete_button=delete_button, | ||
590 | + widget_config=self.widget_config, typedef=td.typedef) | ||
591 | + | ||
592 | + | ||
593 | + return XSDComplexTypeFrame(td.type, name=td.name, parent=parent,\ | ||
594 | + schema=self.schema, | ||
595 | + delete_button=delete_button, | ||
596 | + collapsable=True, | ||
597 | + widget_config=self.widget_config, | ||
598 | + typedef=td.typedef) | ||
599 | + | ||
270 | def get_value(self): | 600 | def get_value(self): |
271 | return "" | 601 | return "" |
272 | def add_content(self, root, content): | 602 | def add_content(self, root, content): |
@@ -276,31 +606,46 @@ class XSDComplexTypeFrame(XSDInputField): | @@ -276,31 +606,46 @@ class XSDComplexTypeFrame(XSDInputField): | ||
276 | else: | 606 | else: |
277 | if content is not None: | 607 | if content is not None: |
278 | root.append(content) | 608 | root.append(content) |
279 | - def get_content(self, obj=None): | 609 | + def get_attribute_values(self): |
610 | + if self.attribute_frame is None: | ||
611 | + return {} | ||
612 | + return self.attribute_frame.get_attribute_values() | ||
613 | + def get_content(self, obj=None, nsmap=None, qname_attrs=None): | ||
280 | if obj is not None: | 614 | if obj is not None: |
281 | - from xsd2tkform.ui.choice import ChoiceInput | 615 | + from .choice import ChoiceInput |
282 | if isinstance(obj, list): | 616 | if isinstance(obj, list): |
283 | - return [self.get_content(i) for i in obj] | ||
284 | - if isinstance(obj, XSDSimpleTypeFrame): | 617 | + return [self.get_content(i, nsmap=nsmap) for i in obj] |
618 | + if isinstance(obj, XSDAnyInputFrame): | ||
285 | if obj.winfo_exists(): | 619 | if obj.winfo_exists(): |
286 | return obj.get_content() | 620 | return obj.get_content() |
621 | + if isinstance(obj, XSDSimpleTypeFrame): | ||
622 | + if obj.winfo_exists(): | ||
623 | + return obj.get_content(nsmap=nsmap) | ||
287 | if isinstance(obj, XSDComplexTypeFrame): | 624 | if isinstance(obj, XSDComplexTypeFrame): |
288 | if obj.winfo_exists(): | 625 | if obj.winfo_exists(): |
289 | - return obj.get_content() | 626 | + return obj.get_content(nsmap=nsmap) |
290 | if isinstance(obj, ChoiceInput): | 627 | if isinstance(obj, ChoiceInput): |
291 | if obj.winfo_exists(): | 628 | if obj.winfo_exists(): |
292 | - return obj.get_content() | ||
293 | - from xsd2tkform.ui.optional import OptionalInput | 629 | + return obj.get_content(nsmap=nsmap) |
630 | + from .optional import OptionalInput | ||
294 | if isinstance(obj, OptionalInput): | 631 | if isinstance(obj, OptionalInput): |
295 | if obj.winfo_exists(): | 632 | if obj.winfo_exists(): |
296 | - return obj.get_content() | 633 | + return obj.get_content(nsmap=nsmap) |
297 | 634 | ||
298 | return | 635 | return |
299 | 636 | ||
300 | - | ||
301 | - root = etree.Element(self.sanitize_type(self.type)) | 637 | + if nsmap is None: |
638 | + root = etree.Element(self.sanitize_type(self.type)) | ||
639 | + else: | ||
640 | + root = etree.Element(self.sanitize_type(self.type), qname_attrs, nsmap=nsmap) | ||
641 | + attrib_values=self.get_attribute_values() | ||
642 | + for k in attrib_values: | ||
643 | + root.set(k, attrib_values[k]) | ||
302 | # returns tree type | 644 | # returns tree type |
303 | - for c in self.get_content(self.inputs): | 645 | + for c in self.get_content(self.inputs, nsmap=nsmap): |
646 | + if isinstance(c, str): | ||
647 | + root.text=c | ||
648 | + continue | ||
304 | self.add_content(root, c) | 649 | self.add_content(root, c) |
305 | return root | 650 | return root |
306 | 651 |
xsd2tkform/ui/datetime_selector.py
@@ -31,5 +31,27 @@ class DatetimeEntry(tk.Frame): | @@ -31,5 +31,27 @@ class DatetimeEntry(tk.Frame): | ||
31 | def get(self): | 31 | def get(self): |
32 | # return time value | 32 | # return time value |
33 | return "{}T{}:{}:{}Z".format(self.date.get(), self.hours.get(), self.minutes.get(), self.seconds.get()) | 33 | return "{}T{}:{}:{}Z".format(self.date.get(), self.hours.get(), self.minutes.get(), self.seconds.get()) |
34 | - | 34 | + def set(self, content): |
35 | + content=content.strip() | ||
36 | + from datetime import datetime | ||
37 | + if content.endswith("Z"): | ||
38 | + if "." in content: | ||
39 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%S.%fZ") | ||
40 | + else: | ||
41 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%SZ") | ||
42 | + else: | ||
43 | + if "." in content: | ||
44 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%S.%f") | ||
45 | + else: | ||
46 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%S") | ||
47 | + | ||
48 | + | ||
49 | + self.date.set_date(d) | ||
50 | + t=d.time() | ||
51 | + self.hours.delete(0,"end") | ||
52 | + self.minutes.delete(0,"end") | ||
53 | + self.seconds.delete(0,"end") | ||
54 | + self.hours.insert(0, "{0:02d}".format(t.hour)) | ||
55 | + self.minutes.insert(0, "{0:02d}".format(t.minute)) | ||
56 | + self.seconds.insert(0, "{0:02d}".format(t.second)) | ||
35 | 57 |
@@ -0,0 +1,73 @@ | @@ -0,0 +1,73 @@ | ||
1 | +"""Some Entry widgets with validation depending on type | ||
2 | +""" | ||
3 | +import tkinter as tk | ||
4 | + | ||
5 | + | ||
6 | +class FloatEntry(tk.Entry): | ||
7 | + def __init__(self, parent): | ||
8 | + tk.Entry.__init__(self, parent) | ||
9 | + vcmd = (self.register(self.validate), | ||
10 | + '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W') | ||
11 | + self.configure(validate="all", validatecommand=vcmd) | ||
12 | + | ||
13 | + def validate(self, *args): | ||
14 | + try: | ||
15 | + i=float(args[2]) | ||
16 | + self.config(fg="black") | ||
17 | + return True | ||
18 | + except: | ||
19 | + self.config(fg="red") | ||
20 | + return True | ||
21 | + def get(self, *args, **kwargs): | ||
22 | + return float(self.get(*args, **kwargs)) | ||
23 | + def set(self, value): | ||
24 | + self.delete(0, tk.END) | ||
25 | + self.insert(0, value) | ||
26 | + | ||
27 | + | ||
28 | +class IntEntry(tk.Entry): | ||
29 | + def __init__(self, parent): | ||
30 | + tk.Entry.__init__(self, parent) | ||
31 | + vcmd = (self.register(self.validate), | ||
32 | + '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W') | ||
33 | + self.configure(validate="all", validatecommand=vcmd) | ||
34 | + def set(self, value): | ||
35 | + self.delete(0, tk.END) | ||
36 | + self.insert(0, value) | ||
37 | + def get(self, *args, **kwargs): | ||
38 | + return int(self.get(*args, **kwargs)) | ||
39 | + | ||
40 | + | ||
41 | + def validate(self, *args): | ||
42 | + try: | ||
43 | + i=int(args[2]) | ||
44 | + self.config(fg="black") | ||
45 | + return True | ||
46 | + except: | ||
47 | + self.config(fg="red") | ||
48 | + return True | ||
49 | + | ||
50 | +class BoolEntry(tk.Frame): | ||
51 | + def __init__(self, parent): | ||
52 | + tk.Frame.__init__(self, parent)#highlightbackground="bleu", highlightthickness=2) | ||
53 | + vals = ["true", "false"] | ||
54 | + labels=["True", "False"] | ||
55 | + self.var = tk.StringVar() | ||
56 | + self.var.set(vals[0]) | ||
57 | + for i in range(2): | ||
58 | + tk.Radiobutton(self, variable=self.var, text=labels[i], value=vals[i]).pack(side=tk.LEFT, fill=tk.X, expand=1) | ||
59 | + def get(self, *args): | ||
60 | + return self.var.get() | ||
61 | + def set(self, value): | ||
62 | + self.var.set(value) | ||
63 | + | ||
64 | +if __name__=="__main__": | ||
65 | + root = tk.Tk() | ||
66 | + | ||
67 | + float_entry = FloatEntry(root) | ||
68 | + float_entry.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
69 | + | ||
70 | + int_entry = IntEntry(root) | ||
71 | + int_entry.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
72 | + | ||
73 | + root.mainloop() |
xsd2tkform/ui/field.py
@@ -4,10 +4,18 @@ import tkinter as tk | @@ -4,10 +4,18 @@ import tkinter as tk | ||
4 | 4 | ||
5 | 5 | ||
6 | class XSDInputField(tk.Frame): | 6 | class XSDInputField(tk.Frame): |
7 | - def __init__(self, parent, *args, **kwargs): | 7 | + def __init__(self, parent, content=None, widget_config={}, on_delete=None, *args, **kwargs): |
8 | tk.Frame.__init__(self, parent, *args, **kwargs) | 8 | tk.Frame.__init__(self, parent, *args, **kwargs) |
9 | + self.widget_config=widget_config | ||
10 | + self.on_delete = on_delete | ||
11 | + if content is not None: | ||
12 | + self.set_content(content) | ||
9 | def sanitize_type(self, t): | 13 | def sanitize_type(self, t): |
14 | + if t is None: | ||
15 | + return "?" | ||
10 | if ":" in t: | 16 | if ":" in t: |
11 | return t.split(":")[-1] | 17 | return t.split(":")[-1] |
12 | return t | 18 | return t |
19 | + def set_content(self, content=None): | ||
20 | + print("XSDInputField::set_content {}".format(content)) | ||
13 | 21 |
xsd2tkform/ui/optional.py
1 | import tkinter as tk | 1 | import tkinter as tk |
2 | from tkinter.messagebox import showerror | 2 | from tkinter.messagebox import showerror |
3 | 3 | ||
4 | -from xsd2tkform.ui.field import XSDInputField | ||
5 | -from xsd2tkform.ui.tooltip import ToolTip | 4 | +from .field import XSDInputField |
5 | +from .tooltip import ToolTip | ||
6 | 6 | ||
7 | -from xsd2tkform.core.type import SimpleType, ComplexType | 7 | +from ..core.type import SimpleType, ComplexType |
8 | 8 | ||
9 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | ||
10 | -from xsd2tkform.ui.complextype import XSDComplexTypeFrame | 9 | +from .simpletype import XSDSimpleTypeFrame |
10 | +from .complextype import XSDComplexTypeFrame | ||
11 | 11 | ||
12 | 12 | ||
13 | class OptionalInput(XSDInputField): | 13 | class OptionalInput(XSDInputField): |
14 | - def __init__(self, parent, item, schema, bounds=None, *args, **kwargs): | ||
15 | - XSDInputField.__init__(self, parent , *args, **kwargs) | 14 | + def __init__(self, parent, item, schema, bounds=None, on_add_field=None, on_delete_field=None, *args, **kwargs): |
15 | + XSDInputField.__init__(self, parent , \ | ||
16 | + highlightbackground="red",\ | ||
17 | + highlightthickness=1,\ | ||
18 | + | ||
19 | + *args, **kwargs) | ||
20 | + self.grid_columnconfigure(0, weight=1) | ||
16 | self.count=0 | 21 | self.count=0 |
17 | self.bounds=bounds | 22 | self.bounds=bounds |
18 | self.type = item.type | 23 | self.type = item.type |
24 | + self.item=item | ||
19 | self.schema = schema | 25 | self.schema = schema |
26 | + | ||
20 | self.fields=[] | 27 | self.fields=[] |
21 | - b=tk.Button(self, text="Add {}".format(self.sanitize_type(item.type)), command=self.add_field) | ||
22 | - # add tooltip | ||
23 | - doc_str = self.get_type_docstring(item.type) | 28 | + self.add_button2=None |
29 | + self.next_row=0 | ||
30 | + self.label_frame=None | ||
31 | + self.label=None | ||
32 | + | ||
33 | + self.on_add_field=on_add_field | ||
34 | + self.on_delete_field=on_delete_field | ||
35 | + def is_full(self): | ||
36 | + return self.count == self.bounds[1] | ||
37 | + def remove_label(self): | ||
38 | + if self.label is not None: | ||
39 | + self.label.destroy() | ||
40 | + self.label=None | ||
41 | + | ||
42 | + def set_label_frame(self, frame): | ||
43 | + self.label_frame=frame | ||
44 | + | ||
45 | + def get_add_button(self, parent): | ||
46 | + button_text=self.sanitize_type(self.type) | ||
47 | + if button_text in ["float", "string", "integer", "?"]: | ||
48 | + button_text=self.item.name | ||
49 | + self.add_button2= tk.Button(parent, text=button_text, command=self.add_field) | ||
50 | + doc_str = self.get_type_docstring(self.type) | ||
24 | if len(doc_str): | 51 | if len(doc_str): |
25 | - ttt = ToolTip(b, doc_str) | ||
26 | - b.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
27 | - def add_field(self): | 52 | + ttt = ToolTip(self.add_button2, doc_str) |
53 | + | ||
54 | + | ||
55 | + return self.add_button2 | ||
56 | + def set_content(self, content, update_grid=True): | ||
57 | + self.add_field(content=content, update_grid=update_grid) | ||
58 | + def add_label(self): | ||
59 | + if self.label_frame is not None and self.label is None: | ||
60 | + self.label=tk.Label(self.label_frame, text=self.sanitize_type(self.type)+":") | ||
61 | + self.label.grid() | ||
62 | + def get_fields(self): | ||
63 | + self.fields = [w for w in self.fields if w.winfo_exists()] | ||
64 | + return self.fields | ||
65 | + def add_field(self, content=None, update_grid=True): | ||
28 | if self.bounds[1]=="unbounded": | 66 | if self.bounds[1]=="unbounded": |
29 | - print("No limit on {} fields".format(self.type)) | 67 | + pass |
30 | elif self.count==self.bounds[1]: | 68 | elif self.count==self.bounds[1]: |
31 | showerror(title="{} maximum occurences reached".format(self.type), message="A maximum of {} occurences of type {}".format(self.bounds[1], self.type)) | 69 | showerror(title="{} maximum occurences reached".format(self.type), message="A maximum of {} occurences of type {}".format(self.bounds[1], self.type)) |
32 | return | 70 | return |
33 | else: | 71 | else: |
34 | pass | 72 | pass |
35 | - | 73 | + self.add_label() |
36 | self.count+=1 | 74 | self.count+=1 |
37 | - | ||
38 | - f=self.get_frame_by_type(self.type, delete_button=True) | ||
39 | - f.pack(side=tk.TOP, fill=tk.X, expand=1) | ||
40 | - #self.fields.append(f.winfo_children()[0]) | 75 | + |
76 | + if self.count <= self.bounds[0]: | ||
77 | + f=self.get_frame_by_type(self.type, delete_button=False, content=content, parent=self.master, update_grid=update_grid) | ||
78 | + else: | ||
79 | + f=self.get_frame_by_type(self.type, delete_button=True, content=content, parent=self.master, update_grid=update_grid) | ||
80 | + if f is None: | ||
81 | + return | ||
41 | self.fields.append(f) | 82 | self.fields.append(f) |
42 | - def get_frame_by_type(self, t, parent=None, delete_button=False): | 83 | + if isinstance(f, XSDComplexTypeFrame): |
84 | + f.collapse() | ||
85 | + | ||
86 | + | ||
87 | + # if the maximum number of fields of this type is attained then remove the add button | ||
88 | + if self.count == self.bounds[1] and self.add_button2 is not None: | ||
89 | + self.add_button2.grid_remove() | ||
90 | + | ||
91 | + # update grid in parent | ||
92 | + if content is None: | ||
93 | + if self.on_add_field is not None: | ||
94 | + self.on_add_field() | ||
95 | + #self.master.update_grid() | ||
96 | + | ||
97 | + def get_frame_by_type(self, t, parent=None, delete_button=False, content=None, update_grid=True): | ||
43 | if parent is None: | 98 | if parent is None: |
44 | parent = self | 99 | parent = self |
45 | 100 | ||
46 | td=self.schema.get(t) | 101 | td=self.schema.get(t) |
102 | + if td is None: | ||
103 | + # if type is native type | ||
104 | + ttt=t.split(":")[-1] | ||
105 | + if ttt=="string" or ttt=="float" or ttt=="integer" or ttt=="int": | ||
106 | + # return a SimpleType object | ||
107 | + from ..core.restriction import Restriction | ||
108 | + #from ..core.type import SimpleType | ||
109 | + td=SimpleType(self.item.name, restriction=Restriction(base="string")) | ||
110 | + #return XSDSimpleTypeFrame(self.item.name, name=self.item.name, parent=parent,\ | ||
111 | + return XSDSimpleTypeFrame(ttt, name=self.item.name, parent=parent,\ | ||
112 | + schema=self.schema, delete_button=delete_button,\ | ||
113 | + widget_config=self.widget_config,\ | ||
114 | + typedef=td,\ | ||
115 | + on_delete=self.delete_field) | ||
116 | + | ||
117 | + | ||
47 | if isinstance(td, SimpleType):# in self.simple_types: | 118 | if isinstance(td, SimpleType):# in self.simple_types: |
48 | return XSDSimpleTypeFrame(t, parent=parent,\ | 119 | return XSDSimpleTypeFrame(t, parent=parent,\ |
49 | schema=self.schema, | 120 | schema=self.schema, |
50 | - delete_button=delete_button) | 121 | + delete_button=delete_button, |
122 | + content=content, | ||
123 | + widget_config=self.widget_config, | ||
124 | + on_delete=self.delete_field) | ||
51 | elif isinstance(td, ComplexType): | 125 | elif isinstance(td, ComplexType): |
52 | return XSDComplexTypeFrame(t, parent=parent,\ | 126 | return XSDComplexTypeFrame(t, parent=parent,\ |
53 | schema=self.schema, | 127 | schema=self.schema, |
54 | - delete_button=delete_button) | 128 | + delete_button=delete_button, |
129 | + content=content, | ||
130 | + collapsable=True, | ||
131 | + widget_config=self.widget_config, | ||
132 | + on_delete=self.delete_field) | ||
55 | else: | 133 | else: |
56 | # TODO : add Group support | 134 | # TODO : add Group support |
57 | print("Group support not yet implemented") | 135 | print("Group support not yet implemented") |
58 | - def delete_field(self, t, widget): | ||
59 | - print("in delete_field : {}".format(type(widget))) | ||
60 | - print(self.fields) | ||
61 | - widget.destroy() | ||
62 | - self.count-=1 | ||
63 | - def get_content(self): | ||
64 | - return [i.get_content() for i in self.fields if i.winfo_exists()] | 136 | + def delete_field(self, t=None, widget=None): |
137 | + # doesn't seem to be used | ||
138 | + self.fields = [w for w in self.fields if w.winfo_exists()] | ||
139 | + self.decrement_field_count_by_type(self.type) | ||
140 | + #self.count-=1 | ||
141 | + | ||
142 | + if self.bounds[1]!="unbounded": | ||
143 | + if self.bounds[1]-self.count == 1: | ||
144 | + #self.add_button2.pack(side=tk.LEFT) | ||
145 | + self.add_button2.grid() | ||
146 | + if self.on_delete_field is not None: | ||
147 | + self.on_delete_field() | ||
148 | + #self.master.update_grid() | ||
149 | + def get_content(self, nsmap=None, qname_attrs=None): | ||
150 | + return [i.get_content(nsmap=nsmap, qname_attrs=qname_attrs) for i in self.fields if i.winfo_exists()] | ||
65 | def get_type_docstring(self, t): | 151 | def get_type_docstring(self, t): |
152 | + if t is None: | ||
153 | + return "" | ||
66 | td=self.schema.get(t) | 154 | td=self.schema.get(t) |
155 | + if td is None or (not hasattr(td, "annotation")): | ||
156 | + return "" | ||
67 | a=td.annotation.documentation | 157 | a=td.annotation.documentation |
68 | if len(a): | 158 | if len(a): |
69 | ls=[l for l in a] | 159 | ls=[l for l in a] |
70 | return td.annotation.documentation[ls[0]] | 160 | return td.annotation.documentation[ls[0]] |
71 | return "" | 161 | return "" |
72 | - def decrement_field_count_by_type(self, t): | 162 | + def decrement_field_count_by_type(self, t, widget=None): |
73 | self.count-=1 | 163 | self.count-=1 |
74 | - | ||
75 | 164 |
xsd2tkform/ui/scrollframe.py
@@ -7,7 +7,7 @@ import platform | @@ -7,7 +7,7 @@ import platform | ||
7 | class ScrollFrame(tk.Frame): | 7 | class ScrollFrame(tk.Frame): |
8 | def __init__(self, parent): | 8 | def __init__(self, parent): |
9 | super().__init__(parent) # create a frame (self) | 9 | super().__init__(parent) # create a frame (self) |
10 | - | 10 | + self.bind_ids=[] |
11 | self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff") #place canvas on self | 11 | self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff") #place canvas on self |
12 | self.viewPort = tk.Frame(self.canvas, background="#ffffff") #place a frame on the canvas, this frame will hold the child widgets | 12 | self.viewPort = tk.Frame(self.canvas, background="#ffffff") #place a frame on the canvas, this frame will hold the child widgets |
13 | self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self | 13 | self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self |
@@ -18,11 +18,11 @@ class ScrollFrame(tk.Frame): | @@ -18,11 +18,11 @@ class ScrollFrame(tk.Frame): | ||
18 | self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw", #add view port frame to canvas | 18 | self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw", #add view port frame to canvas |
19 | tags="self.viewPort") | 19 | tags="self.viewPort") |
20 | 20 | ||
21 | - self.viewPort.bind("<Configure>", self.onFrameConfigure) #bind an event whenever the size of the viewPort frame changes. | ||
22 | - self.canvas.bind("<Configure>", self.onCanvasConfigure) #bind an event whenever the size of the canvas frame changes. | 21 | + self.bind_ids.append((self.viewPort, self.viewPort.bind("<Configure>", self.onFrameConfigure))) #bind an event whenever the size of the viewPort frame changes. |
22 | + self.bind_ids.append((self.canvas, self.canvas.bind("<Configure>", self.onCanvasConfigure))) #bind an event whenever the size of the canvas frame changes. | ||
23 | 23 | ||
24 | - self.viewPort.bind('<Enter>', self.onEnter) # bind wheel events when the cursor enters the control | ||
25 | - self.viewPort.bind('<Leave>', self.onLeave) # unbind wheel events when the cursorl leaves the control | 24 | + self.bind_ids.append((self.viewPort, self.viewPort.bind('<Enter>', self.onEnter))) # bind wheel events when the cursor enters the control |
25 | + self.bind_ids.append((self.viewPort, self.viewPort.bind('<Leave>', self.onLeave))) # unbind wheel events when the cursorl leaves the control | ||
26 | 26 | ||
27 | self.onFrameConfigure(None) #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize | 27 | self.onFrameConfigure(None) #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize |
28 | 28 | ||
@@ -49,18 +49,25 @@ class ScrollFrame(tk.Frame): | @@ -49,18 +49,25 @@ class ScrollFrame(tk.Frame): | ||
49 | 49 | ||
50 | def onEnter(self, event): # bind wheel events when the cursor enters the control | 50 | def onEnter(self, event): # bind wheel events when the cursor enters the control |
51 | if platform.system() == 'Linux': | 51 | if platform.system() == 'Linux': |
52 | - self.canvas.bind_all("<Button-4>", self.onMouseWheel) | ||
53 | - self.canvas.bind_all("<Button-5>", self.onMouseWheel) | 52 | + self.bind_ids.append((self.canvas,self.canvas.bind_all("<Button-4>", self.onMouseWheel))) |
53 | + self.bind_ids.append((self.canvas,self.canvas.bind_all("<Button-5>", self.onMouseWheel))) | ||
54 | else: | 54 | else: |
55 | - self.canvas.bind_all("<MouseWheel>", self.onMouseWheel) | 55 | + self.bind_ids.append((self.canvas,self.canvas.bind_all("<MouseWheel>", self.onMouseWheel))) |
56 | 56 | ||
57 | - def onLeave(self, event): # unbind wheel events when the cursorl leaves the control | 57 | + def onLeave(self, event): # unbind wheel events when the cursorl leaves the control |
58 | if platform.system() == 'Linux': | 58 | if platform.system() == 'Linux': |
59 | self.canvas.unbind_all("<Button-4>") | 59 | self.canvas.unbind_all("<Button-4>") |
60 | self.canvas.unbind_all("<Button-5>") | 60 | self.canvas.unbind_all("<Button-5>") |
61 | else: | 61 | else: |
62 | self.canvas.unbind_all("<MouseWheel>") | 62 | self.canvas.unbind_all("<MouseWheel>") |
63 | - | 63 | + def destroy(self): |
64 | + # unbind all | ||
65 | + for w,fid in self.bind_ids: | ||
66 | + w.unbind(fid) | ||
67 | + self.canvas.unbind_all("<Button-4>") | ||
68 | + self.canvas.unbind_all("<Button-5>") | ||
69 | + self.canvas.unbind_all("<MouseWheel>") | ||
70 | + super().destroy() | ||
64 | 71 | ||
65 | 72 | ||
66 | # ******************************** | 73 | # ******************************** |
xsd2tkform/ui/simpletype.py
@@ -3,22 +3,142 @@ from tkinter import ttk | @@ -3,22 +3,142 @@ from tkinter import ttk | ||
3 | 3 | ||
4 | from lxml import etree | 4 | from lxml import etree |
5 | 5 | ||
6 | -from xsd2tkform.ui.tooltip import ToolTip | 6 | +from .tooltip import ToolTip |
7 | 7 | ||
8 | from tkcalendar import DateEntry | 8 | from tkcalendar import DateEntry |
9 | 9 | ||
10 | -from xsd2tkform.ui.datetime_selector import DatetimeEntry | 10 | +from .datetime_selector import DatetimeEntry |
11 | 11 | ||
12 | from .field import XSDInputField | 12 | from .field import XSDInputField |
13 | + | ||
14 | +from amda_xml_manager import delete_image_file | ||
15 | + | ||
16 | +from .entrywidgets import IntEntry, FloatEntry, BoolEntry | ||
17 | + | ||
18 | +class XSDAnyInputFrame(XSDInputField): | ||
19 | + def __init__(self, parent, input_widget_type, content=None): | ||
20 | + XSDInputField.__init__(self, parent) | ||
21 | + | ||
22 | + self.label=None | ||
23 | + self.add_button=None | ||
24 | + | ||
25 | + self.grid_columnconfigure(0, weight=1) | ||
26 | + self.input_widget = input_widget_type(self) | ||
27 | + self.input_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) | ||
28 | + | ||
29 | + self.set_content(content) | ||
30 | + def set_content(self, content=None): | ||
31 | + if content is None: | ||
32 | + return | ||
33 | + if isinstance(content, etree._Element): | ||
34 | + self.input_widget.insert(0, etree.tostring(content, pretty_print=True)) | ||
35 | + return | ||
36 | + self.input_widget.insert(0, str(content)) | ||
37 | + | ||
38 | + | ||
39 | + def get_label(self, parent): | ||
40 | + label_text="any :" | ||
41 | + self.label = tk.Label(parent, text=label_text) | ||
42 | + return self.label | ||
43 | + def get_content(self): | ||
44 | + return self.input_widget.get("1.0", tk.END) | ||
45 | + | ||
46 | + | ||
47 | +class XSDAttributeFrame(XSDInputField): | ||
48 | + def __init__(self, attribute, parent, on_delete=None, on_add=None): | ||
49 | + XSDInputField.__init__(self, parent) | ||
50 | + | ||
51 | + self.on_delete=on_delete | ||
52 | + self.on_add=on_add | ||
53 | + self.name = attribute.name | ||
54 | + self.type = attribute.type | ||
55 | + self.attribute = attribute | ||
56 | + if attribute.use=="required": | ||
57 | + self.mandatory = True | ||
58 | + else: | ||
59 | + self.mandatory = False | ||
60 | + self.label=None | ||
61 | + self.add_button=None | ||
62 | + | ||
63 | + self.grid_columnconfigure(0, weight=1) | ||
64 | + if self.type.endswith("float"): | ||
65 | + self.input_widget=FloatEntry(self) | ||
66 | + elif self.type.endswith("int") or self.type.endswith("integer"): | ||
67 | + self.input_widget=IntEntry(self) | ||
68 | + elif self.type.endswith("boolean"): | ||
69 | + self.input_widget=BoolEntry(self) | ||
70 | + else: | ||
71 | + self.input_widget=tk.Entry(self) | ||
72 | + self.input_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) | ||
73 | + | ||
74 | + if not self.mandatory: | ||
75 | + self.delete_img = tk.PhotoImage(file=delete_image_file) | ||
76 | + #self.db = tk.Button(self, text="X", command=self.delete) | ||
77 | + self.db = tk.Button(self, image=self.delete_img, command=self.delete) | ||
78 | + self.db.pack(side=tk.RIGHT, expand=0) | ||
79 | + def is_visible(self): | ||
80 | + return "row" in self.grid_info() | ||
81 | + def get(self): | ||
82 | + return self.input_widget.get() | ||
83 | + def set(self, value): | ||
84 | + if isinstance(self.input_widget, FloatEntry) or isinstance(self.input_widget, IntEntry) or \ | ||
85 | + isinstance(self.input_widget, BoolEntry): | ||
86 | + self.input_widget.set(value) | ||
87 | + else: | ||
88 | + self.input_widget.insert(0, value) | ||
89 | + def get_add_button(self, parent): | ||
90 | + self.add_button = tk.Button(parent, text=self.name, command=self.add) | ||
91 | + return self.add_button | ||
92 | + def add(self): | ||
93 | + # remove the button | ||
94 | + self.grid() | ||
95 | + if self.label is not None: | ||
96 | + self.label.grid() | ||
97 | + if self.add_button is not None: | ||
98 | + self.add_button.grid_remove() | ||
99 | + if self.on_add is not None: | ||
100 | + self.on_add() | ||
101 | + def delete(self, *args): | ||
102 | + self.grid_remove() | ||
103 | + if self.label is not None: | ||
104 | + self.label.grid_remove() | ||
105 | + # replace the add button | ||
106 | + if self.add_button is not None: | ||
107 | + self.add_button.grid() | ||
108 | + | ||
109 | + if self.on_delete is not None: | ||
110 | + self.on_delete() | ||
111 | + | ||
112 | + def get_label(self, parent): | ||
113 | + label_text=self.sanitize_type(self.name) | ||
114 | + label_text+=" :" | ||
115 | + self.label = tk.Label(parent, text=label_text) | ||
116 | + return self.label | ||
117 | + | ||
13 | class XSDSimpleTypeFrame(XSDInputField): | 118 | class XSDSimpleTypeFrame(XSDInputField): |
14 | - def __init__(self, typename, parent=None, schema=None, delete_button=False, *args, **kwargs): | ||
15 | - XSDInputField.__init__(self, parent, *args, **kwargs) | 119 | + def __init__(self, typename, parent=None, schema=None, delete_button=False, filename=None, content=None, name=None, widget_config={}, typedef=None, input_widget=None,*args, **kwargs): |
120 | + XSDInputField.__init__(self, parent, widget_config=widget_config,\ | ||
121 | + # highlightbackground="blue",\ | ||
122 | + # highlightthickness=1,\ | ||
123 | + *args, **kwargs) | ||
124 | + # flag indicating that the content has been set | ||
125 | + self.content_has_been_set = False | ||
126 | + | ||
127 | + self.grid_columnconfigure(0, weight=1) | ||
128 | + self.input_widget_type=input_widget | ||
16 | 129 | ||
17 | self.type = typename | 130 | self.type = typename |
18 | - self.label = tk.Label(self, text="{}: ".format(self.sanitize_type(typename))) | ||
19 | - self.label.pack(side=tk.LEFT, expand=0) | 131 | + self.name = name |
132 | + | ||
133 | + print("XSDSimpleTypeFrame name={}, type={}".format(self.name, self.type)) | ||
134 | + | ||
135 | + # label reference | ||
136 | + self.label=None | ||
20 | 137 | ||
21 | - self.typedef = schema.get(typename) | 138 | + if typedef is None: |
139 | + self.typedef = schema.get(typename) | ||
140 | + else: | ||
141 | + self.typedef = typedef | ||
22 | 142 | ||
23 | self.set_tooltip() | 143 | self.set_tooltip() |
24 | 144 | ||
@@ -27,41 +147,126 @@ class XSDSimpleTypeFrame(XSDInputField): | @@ -27,41 +147,126 @@ class XSDSimpleTypeFrame(XSDInputField): | ||
27 | self.input_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) | 147 | self.input_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) |
28 | 148 | ||
29 | # delete button | 149 | # delete button |
150 | + self.delete_img = None | ||
30 | if delete_button: | 151 | if delete_button: |
31 | - self.db = tk.Button(self, text="X", command=self.delete) | 152 | + self.delete_img = tk.PhotoImage(file=delete_image_file) |
153 | + #self.db = tk.Button(self, text="X", command=self.delete) | ||
154 | + self.db = tk.Button(self, image=self.delete_img, command=self.delete) | ||
32 | self.db.pack(side=tk.RIGHT, expand=0) | 155 | self.db.pack(side=tk.RIGHT, expand=0) |
156 | + | ||
157 | + # if a filename was given then load the content | ||
158 | + if filename is not None: | ||
159 | + print("Setting SimpleType content from file : {}".format(filename)) | ||
160 | + if content is not None: | ||
161 | + self.set_content(content) | ||
162 | + def get_label(self, parent): | ||
163 | + label_text=self.sanitize_type(self.type) | ||
164 | + if label_text in ["string", "float", "integer"]: | ||
165 | + label_text=self.name | ||
166 | + | ||
167 | + if self.name is not None: | ||
168 | + label_text=self.name | ||
169 | + else: | ||
170 | + label_text=self.sanitize_type(self.type) | ||
171 | + if label_text in ["string", "float", "integer"]: | ||
172 | + label_text=self.name | ||
173 | + | ||
174 | + label_text+=" :" | ||
175 | + self.label = tk.Label(parent, text=label_text) | ||
176 | + self.set_tooltip() | ||
177 | + return self.label | ||
33 | def delete(self): | 178 | def delete(self): |
34 | # destroy this widget and decrement the field count in | 179 | # destroy this widget and decrement the field count in |
35 | # the master frame | 180 | # the master frame |
36 | - print("self id : ", id(self)) | ||
37 | - print([id(e) for e in self.master.fields]) | ||
38 | - print(self.master.fields) | ||
39 | - self.master.decrement_field_count_by_type(self.type) | 181 | + #self.master.decrement_field_count_by_type(self.type) |
40 | self.destroy() | 182 | self.destroy() |
183 | + if self.on_delete is not None: | ||
184 | + self.on_delete() | ||
185 | + if self.label is not None: | ||
186 | + self.label.destroy() | ||
187 | + def is_full(self): | ||
188 | + return self.content_has_been_set | ||
189 | + def set_content(self, content, update_grid=True): | ||
190 | + text=content.text | ||
191 | + if text is None: | ||
192 | + text="" | ||
193 | + if isinstance(self.input_widget, ttk.Combobox): | ||
194 | + if not content.text is None: | ||
195 | + self.input_widget.set(content.text) | ||
196 | + elif isinstance(self.input_widget, tk.Text): | ||
197 | + n_lines = len(text.split("\n"))+1 | ||
198 | + self.input_widget.insert("1.0", text) | ||
199 | + #self.input_widget.configure(height=n_lines) | ||
200 | + | ||
201 | + elif isinstance(self.input_widget, tk.Entry): | ||
202 | + self.input_widget.insert(0, text) | ||
203 | + else: | ||
204 | + self.input_widget.set(content.text) | ||
205 | + self.content_has_been_set = True | ||
41 | def set_tooltip(self): | 206 | def set_tooltip(self): |
207 | + if self.label is None: | ||
208 | + return | ||
209 | + if self.typedef is None: | ||
210 | + return | ||
211 | + if self.typedef.annotation is None: | ||
212 | + return | ||
42 | if len(self.typedef.annotation.documentation): | 213 | if len(self.typedef.annotation.documentation): |
43 | langs = [k for k in self.typedef.annotation.documentation] | 214 | langs = [k for k in self.typedef.annotation.documentation] |
44 | - tt = ToolTip(self.label, self.typedef.annotation.documentation[langs[0]]) | 215 | + tt=ToolTip(self.label, self.typedef.annotation.documentation[langs[0]]) |
45 | def get_input_widget(self): | 216 | def get_input_widget(self): |
217 | + if self.input_widget_type is not None: | ||
218 | + return self.input_widget_type(self) | ||
219 | + if self.sanitize_type(self.type) in self.widget_config: | ||
220 | + persot = self.widget_config[self.sanitize_type(self.type)] | ||
221 | + if isinstance(persot, tuple): | ||
222 | + return persot[0](self, *persot[1]) | ||
223 | + return self.widget_config[self.sanitize_type(self.type)](self) | ||
46 | if self.typedef.restriction is not None: | 224 | if self.typedef.restriction is not None: |
47 | if self.typedef.restriction.base=="xsd:string": | 225 | if self.typedef.restriction.base=="xsd:string": |
48 | if len(self.typedef.restriction.enum): | 226 | if len(self.typedef.restriction.enum): |
49 | - b=ttk.Combobox(self, values=self.typedef.restriction.enum, state="readonly") | 227 | + #b=ttk.Combobox(self, values=self.typedef.restriction.enum, state="readonly") |
228 | + from .autocompleteentry import AutocompleteCombobox | ||
229 | + b=AutocompleteCombobox(self) | ||
230 | + b.set_completion_list(self.typedef.restriction.enum) | ||
231 | + | ||
50 | b.current(0) | 232 | b.current(0) |
233 | + b.unbind_class("TCombobox","<MouseWheel>") | ||
234 | + b.unbind_class("TCombobox","<ButtonPress-4>") | ||
235 | + b.unbind_class("TCombobox","<ButtonPress-5>") | ||
236 | + | ||
51 | return b | 237 | return b |
238 | + #if "Description" in self.type: | ||
239 | + # return tk.Text(self) | ||
52 | return tk.Entry(self) | 240 | return tk.Entry(self) |
53 | if self.typedef.restriction.base=="xsd:dateTime": | 241 | if self.typedef.restriction.base=="xsd:dateTime": |
54 | return DatetimeEntry(self) | 242 | return DatetimeEntry(self) |
55 | if len(self.typedef.restriction.enum): | 243 | if len(self.typedef.restriction.enum): |
56 | return ttk.Combobox(self, values=self.typedef.restriction.enum, state="readonly") | 244 | return ttk.Combobox(self, values=self.typedef.restriction.enum, state="readonly") |
245 | + if self.type.endswith("float"): | ||
246 | + return FloatEntry(self) | ||
247 | + if self.type.endswith("int") or self.type.endswith("integer"): | ||
248 | + return IntEntry(self) | ||
57 | return tk.Entry(self) | 249 | return tk.Entry(self) |
58 | 250 | ||
59 | def get_value(self): | 251 | def get_value(self): |
252 | + if isinstance(self.input_widget, tk.Text): | ||
253 | + return self.input_widget.get("1.0",tk.END).strip() | ||
60 | return self.input_widget.get() | 254 | return self.input_widget.get() |
61 | - def get_content(self): | 255 | + def get_attribute_values(self): |
256 | + return {} | ||
257 | + def get_content(self, nsmap=None, qname_attrs=None): | ||
62 | # returns tree type | 258 | # returns tree type |
63 | - t = self.sanitize_type(self.type) | ||
64 | - root = etree.Element(t) | 259 | + if self.name is None: |
260 | + t = self.sanitize_type(self.type) | ||
261 | + else: | ||
262 | + t=self.name | ||
263 | + if nsmap is not None: | ||
264 | + root = etree.Element(t,qname_attrs, nsmap=nsmap) | ||
265 | + else: | ||
266 | + root = etree.Element(t) | ||
267 | + attrib_values=self.get_attribute_values() | ||
268 | + for k in attrib_values: | ||
269 | + root.set(k, attrib_values[k]) | ||
65 | root.text = self.get_value() | 270 | root.text = self.get_value() |
66 | return root | 271 | return root |
67 | 272 |