Improved redraw.
[dom-editor.git] / Dome / xslt.py
blobd11fa176a900ab7c7aff6f3b9516f6f995e7dceb
1 from constants import XSLT_NS
2 from xml.dom import Node
4 from Ft.Xml.Xslt.StylesheetReader import StylesheetReader
5 from Ft.Xml.Xslt.StylesheetTree import XsltElement, XsltText
6 from Ft.Xml.Xslt.LiteralElement import LiteralElement
7 from Ft.Xml.Xslt.ApplyTemplatesElement import ApplyTemplatesElement
8 from Ft.Xml.Xslt.ValueOfElement import ValueOfElement
9 from Ft.Xml.Xslt.CopyElement import CopyElement
10 from Program import Program, Op, Block
12 def mode_prog_name(mode):
13 if mode:
14 return 'Mode:' + mode[1]
15 else:
16 return 'Default mode'
18 def import_sheet(doc):
19 #print "Import!", doc
21 root = Program('XSLT')
23 # The root program puts the mark on the Result node and the cursor on the Source.
24 # It then runs the program for the default mode. There is one program for each mode, and
25 # it acts as a dispatcher. It finds a template which matches the cursor node and runs
26 # the program for that template.
28 op = add(root.code.start, 'do_search', '/xslt/Result')
29 op = add(op, 'mark_selection')
30 op = add(op, 'do_search', '/xslt/Source')
31 op = add(op, 'play', 'XSLT/Default mode')
33 # This program copies a text node to the output
34 prog = Program('DefaultText')
35 root.add_sub(prog)
36 op = add(prog.code.start, 'yank')
37 op = add(op, 'mark_switch')
38 op = add(op, 'put_as_child_end')
39 op = add(op, 'move_left')
40 op = add(op, 'mark_switch')
42 # To start with, the cursor is on the source document node and
43 # the mark is on the result document node.
45 # The mode program is called with:
46 # => Cursor = context node
47 # Mark = result parent (append children here)
48 # <= Cursor is undefined
49 # Mark is unchanged
51 reader = StylesheetReader()
52 sheet = reader.fromDocument(doc)
54 global s
55 s = sheet
57 # sheet.matchTemplates is { mode -> { type -> { (ns, name) -> [match] for elements
58 # { [match] otherwise
60 # Each match is (pattern, axis_type, TemplateElement)
62 # The list of matches is sorted; use the first that matches. Multiple lookups
63 # may be required (eg, lookup 'html' then None (for 'node()' and '*')).
64 # Patterns like 'text()|comment()' are broken down into two match elements.
66 # XXX: Could have two modes with the same name but different namespaces...
68 i = 1
70 for mode in sheet.matchTemplates.keys():
71 mode_name = mode_prog_name(mode)
72 prog = Program(mode_name)
73 root.add_sub(prog)
74 tests = prog.code.start
75 print "Mode", mode
76 types = sheet.matchTemplates[mode]
77 loose_ends = []
78 for type in types.keys():
79 if type == Node.ELEMENT_NODE:
80 templates = types[type].values()
81 else:
82 templates = [types[type]]
83 for tl in templates:
84 for t in tl:
85 pattern = `t[0]`
86 name = pattern.replace('/', '%')
87 temp = Program(`i` + '-' + name)
88 op = add(temp.code.start, 'mark_switch')
89 make_template(op, t[2])
90 i += 1
91 prog.add_sub(temp)
93 if pattern.startswith('/'):
94 if pattern == '/':
95 pattern = ''
96 pattern = '/xslt/Source' + pattern # XXX: Hack
97 tests = add(tests, 'fail_if', pattern)
98 op = Op(action = ['play', temp.get_path()])
99 tests.link_to(op, 'fail')
100 loose_ends.append(op)
101 # Now add the built-in rules
103 #print "Tidy", loose_ends
105 tests = add(tests, 'fail_if', 'text()')
106 op = Op(action = ['play', 'XSLT/DefaultText'])
107 tests.link_to(op, 'fail')
109 tests = add(tests, 'do_global', '*')
110 tests = add(tests, 'map', prog.get_path())
111 tests = add(tests, 'mark_switch')
112 tests = add(tests, 'mark_switch')
113 [ op.link_to(tests, 'next') for op in loose_ends ]
115 root.modified = 0
116 return root
118 def add(op, *action):
119 new = Op(action = action)
120 op.link_to(new, 'next')
121 return new
123 # A template is instantiated by running its program.
125 # => Cursor = result parent (append here)
126 # Mark = context node
128 # <= Cursor is undefined
129 # Mark is unchanged
131 # Add the instructions to instantiate this template to 'op'.
132 def make_template(op, temp):
133 for child in temp.children:
134 if isinstance(child, XsltText):
135 #print "Text node", child.data
136 op = add(op, 'add_node', 'et', child.data)
137 op = add(op, 'move_left')
139 elif isinstance(child, LiteralElement):
140 #print "Element", child._output_qname
141 op = add(op, 'add_node', 'ee', child._output_qname)
142 for (qname, namespace, value) in child._output_attrs:
143 if len(value._parsedParts) != 1:
144 print "TODO: can't handle attrib", value
145 else:
146 xpath = `value._parsedParts[0]`
147 if value._plainParts:
148 # XXX: Do this propertly
149 xpath = 'concat("%s", %s)' % (value._plainParts[0], xpath)
150 print "XPath =", xpath
151 op = add(op, 'mark_switch')
152 op = add(op, 'xpath', xpath)
153 op = add(op, 'mark_switch')
154 op = add(op, 'add_attrib', None, qname)
155 op = add(op, 'put_replace')
157 op = make_template(op, child)
158 op = add(op, 'move_left')
159 elif isinstance(child, ApplyTemplatesElement):
160 block = Block(op.parent)
161 block.toggle_restore()
162 sub = block.start
164 sub = add(sub, 'mark_switch')
165 # Ugly hack... global *|text() doesn't select in document order
166 if child._select:
167 sub = add(sub, 'do_global', `child._select.expression`)
168 else:
169 sub = add(sub, 'select_children')
170 sub = add(sub, 'map', 'XSLT/' + mode_prog_name(child._mode))
171 sub = add(sub, 'mark_switch')
173 op.link_to(block, 'next')
174 op = block
175 elif isinstance(child, ValueOfElement):
176 op = add(op, 'mark_switch')
177 op = add(op, 'xpath', `child._select.expression`)
178 op = add(op, 'mark_switch')
179 op = add(op, 'put_as_child_end')
180 op = add(op, 'move_left')
181 elif isinstance(child, CopyElement):
182 op = add(op, 'mark_switch')
183 op = add(op, 'shallow_yank')
184 op = add(op, 'mark_switch')
185 op = add(op, 'put_as_child_end')
186 op = make_template(op, child)
187 op = add(op, 'move_left')
188 else:
189 print "Unknown template type", child, "(%s)" % child.__class__
190 return op