1 """XML processing utilites for the music_halfspan formalism.
2
3 These include building internal representations from the XML grammar
4 definitions.
5
6 """
7 """
8 ============================== License ========================================
9 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
10
11 This file is part of The Jazz Parser.
12
13 The Jazz Parser is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 The Jazz Parser is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
25
26 ============================ End license ======================================
27
28 """
29 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
30
31 import re, logging
32 from jazzparser.utils.chords import ChordError
33 from jazzparser.grammar import GrammarReadError
34 from .syntax import AtomicCategory, ComplexCategory, Slash, Sign, HalfCategory
35 from .semantics import Semantics, DummyLogicalForm, Leftonto, Variable, \
36 LexicalCoordinate, multi_apply, multi_abstract, Rightonto, Now, \
37 List
38 from jazzparser.utils.domxml import remove_unwanted_elements, \
39 require_attrs, require_attr
40
41
42 logger = logging.getLogger("main_logger")
43
44
46 """
47 Given the XML element of a CCG sign, builds the internal
48 representation.
49
50 """
51
52 child_elements = remove_unwanted_elements(element.childNodes)
53 cat_elem = child_elements[0]
54
55 gramcategory = build_category_from_node(cat_elem)
56
57 lf_elems = element.getElementsByTagName("lf")
58 if len(lf_elems) == 0 or len(remove_unwanted_elements(lf_elems[0].childNodes)) == 0:
59 raise GrammarReadError, "No logical form found for entry: "\
60 "%s. What is syntax without semantics?" % cat_elem.toxml()
61
62 lf_elem = lf_elems[0]
63 lf_children = remove_unwanted_elements(lf_elem.childNodes)
64
65 sems = build_lf_from_node(lf_children[0])
66 if sems is None:
67 raise GrammarReadError, "Could not build semantic " \
68 "representation for %s." % lf_elem.toxml()
69 sems = Semantics(sems)
70
71
72 return Sign(gramcategory, sems)
73
75 """
76 Uses an XML DOM node that represents a CCG category (i.e. a structure
77 of atomcats and complexcats) to build a CCG category in our internal
78 representation.
79
80 @return: a Category subclass that is the root of the category structure
81
82 """
83 halfcat_re = re.compile(r'^(?P<root>.+?)(\[(?P<functions>(T|D|S)(\|(T|D|S))*)\])?$')
84 def _load_halfcat(name):
85 """Try building a HalfCategory from this string"""
86 match = halfcat_re.match(name)
87 if match is None:
88 raise GrammarReadError, "%s is not a valid half-category. Reading element %s." % (name, element.toxml())
89 root = match.groupdict()['root']
90 functions = match.groupdict()['functions']
91 if functions is None:
92
93 functions = ["T"]
94 else:
95 functions = functions.split("|")
96 return HalfCategory(root, functions)
97
98 def _required_attr(el, name):
99 attr_el = element.attributes.getNamedItem(name)
100 if attr_el is None:
101 raise GrammarReadError, "required attribute '%s': %s" % (name, el.toxml())
102 return attr_el.value
103
104 if (element.nodeName== "atomcat"):
105
106
107
108 root_name = _required_attr(element, "root")
109 half_root = _load_halfcat(root_name)
110
111
112 return AtomicCategory(half_root, half_root.copy())
113 elif (element.nodeName == "complexcat"):
114
115
116
117 arg_name = _required_attr(element, "arg")
118 result_name = _required_attr(element,"res")
119 direction = _required_attr(element,"dir")
120 if (direction == "/"):
121 forward = True
122 elif (direction == "\\"):
123 forward = False
124 else:
125 raise GrammarReadError, "%s is not a valid slash direction: should be / or \\" % direction
126
127 mode_el = element.attributes.getNamedItem("modality")
128
129 if mode_el is not None:
130 modality = mode_el.value
131 else:
132 modality = None
133 slash = Slash(forward, modality=modality)
134
135 result = _load_halfcat(result_name)
136 argument = _load_halfcat(arg_name)
137
138 category = ComplexCategory(result, slash, argument)
139 return category
140 else:
141
142 raise GrammarReadError, "Unrecognised category node %s" % element
143
145 """
146 Given the "lf" node of a lexical entry, builds a logical form
147 representing it internally.
148
149 @return: a LogicalForm built from the node
150
151 """
152 name = elem.nodeName
153 if name == "point":
154
155
156 x,y = require_attrs(elem, ["x", "y"])
157 x,y = int(x), int(y)
158
159 if not 0 <= x < 4 or not 0 <= y < 3:
160 raise GrammarReadError, "equal temperament tonal space "\
161 "points should be between (0,0) and (3,2): got (%d,%d)" \
162 % (x,y)
163
164 subnodes = remove_unwanted_elements(elem.childNodes)
165 if len(subnodes) != 0:
166 raise GrammarReadError, "A tonal space point cannot have children."
167
168 return LexicalCoordinate((x,y))
169 elif name == "list":
170
171 subnodes = remove_unwanted_elements(elem.childNodes)
172
173 children = [build_lf_from_node(node) for node in subnodes]
174 return List(children)
175 elif name == "leftonto":
176
177
178
179 subnodes = remove_unwanted_elements(elem.childNodes)
180 if len(subnodes) != 0:
181 raise GrammarReadError, "A leftonto predicate cannot "\
182 "have children."
183
184 return Leftonto()
185 elif name == "rightonto":
186
187
188
189 subnodes = remove_unwanted_elements(elem.childNodes)
190 if len(subnodes) != 0:
191 raise GrammarReadError, "A rightonto predicate cannot "\
192 "have children."
193
194 return Rightonto()
195 elif name == "now":
196
197
198
199 subnodes = remove_unwanted_elements(elem.childNodes)
200 if len(subnodes) != 0:
201 raise GrammarReadError, "A now predicate cannot "\
202 "have children."
203
204 return Now()
205 elif name == "abstraction":
206
207
208 subnodes = remove_unwanted_elements(elem.childNodes)
209 if len(subnodes) < 2:
210 raise GrammarReadError, "No subexpression in lambda "\
211 "abstraction: %s" % elem.toxml()
212 variables = [build_lf_from_node(node) for node in subnodes[:-1]]
213 for var in variables:
214 if not isinstance(var, Variable):
215 raise GrammarReadError, "Can only abstract over "\
216 "variables, not %s" % type(var).__name__
217 expression = build_lf_from_node(subnodes[-1])
218
219 return multi_abstract(*tuple(variables+[expression]))
220 elif name == "application":
221
222
223 subnodes = remove_unwanted_elements(elem.childNodes)
224 if len(subnodes) < 2:
225 raise GrammarReadError, "Function application needs to "\
226 "have at least two subnodes"
227 children = [build_lf_from_node(node) for node in subnodes]
228
229 return multi_apply(*children)
230 elif name == "variable":
231
232 varid = require_attr(elem, "name")
233
234 return Variable(varid)
235 else:
236 raise GrammarReadError, "Got invalid node %s in LF" % name
237