1 """Grammar processing for the Jazz Parser.
2
3 This module is used to read the XML grammar files for the parser.
4 They are stored in the OpenCCG grammar format (roughly). This
5 provides the interface to the grammar and the formalism for the parser
6 and tagger.
7
8 """
9 """
10 ============================== License ========================================
11 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
12
13 This file is part of The Jazz Parser.
14
15 The Jazz Parser is free software: you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation, either version 3 of the License, or
18 (at your option) any later version.
19
20 The Jazz Parser is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
27
28 ============================ End license ======================================
29
30 """
31 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
32
33 import xml.dom.minidom
34 from jazzparser.formalisms.base.modalities import ModalityTree, \
35 ModalityTreeNode
36 from jazzparser.utils.domxml import attrs_to_dict, remove_unwanted_elements, \
37 get_single_element_by_tag_name
38 from jazzparser.utils.chords import generalise_chord_name
39 from jazzparser.data import Chord
40 from jazzparser.data.db_mirrors import Chord as DbChord
41 from jazzparser.formalisms import FORMALISMS
42 from jazzparser.formalisms.loader import get_default_formalism, get_formalism, \
43 FormalismLoadError
44
45 import logging, os, copy
46 import settings
47
48
49 logger = logging.getLogger("main_logger")
52 """
53 Represents a grammar read in from an XML grammar file.
54 Initialised with the location of the XML file.
55 Can be consulted to retrieve lexical entries in the grammar.
56
57 """
58
59
60
61 formalism = None
62 """
63 Formalism definition (L{jazzparser.formalisms.FormalismBase}) for the
64 formalism used by the grammar instance.
65
66 Always set.
67
68 """
69 grammar_file = None
70 """
71 Path to the grammar.xml file from which the grammar definition was loaded.
72
73 Always set.
74
75 """
76 literal_functions = None
77 """
78 Dictionary of builtin semantic literal functions.
79
80 May be None.
81
82 @deprecated: Only used in very early versions of the grammar. Left here
83 for backwards compatibility. May be removed altogether soon.
84
85 """
86 families = None
87 """
88 Dictionary of lexical entry families (each a list of lexical signs),
89 keyed by POS.
90
91 """
92 inactive_families = None
93 """
94 Like families. Dictionary of families defined in the XML but marked as
95 inactive in their definition.
96
97 Always a list. May be empty.
98
99 """
100 morphs = None
101 """
102 List of morph entries (L{MorphItem}). Mapping from word classes to
103 families. You probably don't want to access this directly usually, but
104 to use L{Grammar}'s methods like L{get_signs_for_word}.
105
106 """
107 morph_items = None
108 """
109 Same as L{morphs}, but a dictionary indexed by the word attribute
110 (identifier of word or word class) for easy lookup.
111
112 """
113 modality_tree = None
114 """
115 Instance of L{jazzparser.formalisms.base.modalities.ModalityTree}
116 representing the hierarchical structure of modalities used in this grammar.
117
118 """
119 rules = None
120 """
121 List of all the rules used by the grammar. These are instances of the
122 rule classes, instantiated with the correct parameters and ready to apply
123 to signs. Does not include lexical rules: see L{lexical_rules}.
124
125 """
126 unary_rules = None
127 """
128 List of unary rules. Subset of L{rules}.
129
130 May be empty, but should always be a list.
131
132 """
133 binary_rules = None
134 """
135 List of binary rules. Subset of L{rules}.
136
137 May be empty, but should always be a list.
138
139 """
140 rules_by_name = None
141 """
142 Dictionary of rules, containing same entries as L{rules}, keyed by
143 the internal_name attribute. There should not be more than one rule
144 instance with the same internal name. If multiple are created when the
145 grammar is instantiated, a L{GrammarReadError} will be raised.
146
147 """
148 lexical_rules = None
149 """
150 List of special unary rules that are applied to lexical families when the
151 grammar is instantiated to expand the lexicon. The lexicon will then
152 include all the original lexical entries, plus any results of applying
153 these rules to them.
154
155 Empty list if no lexical rules are given.
156
157 """
158 chord_classes = None
159 """
160 Dict of L{ChordClass} objects, indexed by name.
161
162 """
163
164
166 """
167 Creates a new grammar by reading from an XML grammar file.
168
169 Words (morph items) are stored in morph_items.
170 Families (lexical families) are stored in families.
171
172 Instantiate this directly only if you want, for some reason, to be sure
173 of getting a new instance of Grammar. Most of the time, you can
174 load a named grammar using L{get_grammar}, which will cache already
175 loaded grammars and return the same instance again if you ask for the
176 same name.
177
178 @type grammar_name: string
179 @param grammar_name: name of the grammar definition to be loaded.
180 Call L{get_grammar_names} for a list of available grammars. If
181 None, loads the default grammar.
182
183 """
184 if grammar_name is None:
185 grammar_name = settings.DEFAULT_GRAMMAR
186 self.name = grammar_name
187
188 filename_base = os.path.join(settings.GRAMMAR_DATA_DIR, grammar_name)
189 self.grammar_file = os.path.join(filename_base, "grammar.xml")
190
191 logger.debug("Grammar: %s" % self.grammar_file)
192
193
194 self.grammar_dom = xml.dom.minidom.parse(self.grammar_file)
195
196 grammar_tag = get_single_element_by_tag_name(self.grammar_dom, "grammar")
197
198 formalism_attr = grammar_tag.attributes.getNamedItem("formalism")
199 if formalism_attr is None:
200 formalism = get_default_formalism()
201 else:
202 formalism_name = str(formalism_attr.value)
203 try:
204 formalism = get_formalism(formalism_name)
205 except FormalismLoadError:
206 logger.error("The formalism '%s' does not exist. Possible "\
207 "formalisms are: %s" % (formalism_name, ", ".join(FORMALISMS)))
208 raise
209 self.formalism = formalism
210
211
212
213 lex_tag = get_single_element_by_tag_name(self.grammar_dom, "lexicon")
214 lexicon_file = os.path.join(filename_base, lex_tag.attributes.getNamedItem("file").value)
215 logger.debug("Lexicon: %s" % lexicon_file)
216
217 self.lexicon_dom = xml.dom.minidom.parse(lexicon_file)
218
219
220
221 morph_tag = get_single_element_by_tag_name(self.grammar_dom, "morphology")
222 morph_file = os.path.join(filename_base, morph_tag.attributes.getNamedItem("file").value)
223 logger.debug( "Morphology: %s" % morph_file)
224
225 self.morph_dom = xml.dom.minidom.parse(morph_file)
226
227
228
229 rules_tag = get_single_element_by_tag_name(self.grammar_dom, "rules")
230 rules_file = os.path.join(filename_base, rules_tag.attributes.getNamedItem("file").value)
231 logger.debug( "Rules: %s" % rules_file)
232
233 self.rules_dom = xml.dom.minidom.parse(rules_file)
234
235
236
237 functions_tag = get_single_element_by_tag_name(self.grammar_dom, "functions", optional=True)
238 self.literal_functions = {}
239 available_funs = formalism.literal_functions
240 if functions_tag is not None:
241 functions_file = os.path.join(filename_base, functions_tag.attributes.getNamedItem("file").value)
242 logger.debug( "Functions: %s" % functions_file)
243
244 functions_dom = xml.dom.minidom.parse(functions_file)
245 functions_xml = get_single_element_by_tag_name(functions_dom, "functions")
246 functions = remove_unwanted_elements(functions_xml.getElementsByTagName("function"))
247
248 for func_el in functions:
249 func_name = func_el.attributes.getNamedItem("name").value
250 if func_name in available_funs:
251 lit_fun = available_funs[func_name]
252 self.literal_functions[lit_fun.name] = lit_fun
253 else:
254 raise GrammarReadError, "The literal function \"%s\" is not defined in the code for the %s formalism." % formalism.get_name()
255
256
257
258 modalities_tag = get_single_element_by_tag_name(self.grammar_dom, "modalities", optional=True)
259 if modalities_tag is not None:
260 modalities_file = os.path.join(filename_base, modalities_tag.attributes.getNamedItem("file").value)
261 logger.debug( "Modalities: %s" % modalities_file)
262
263 self.modalities_dom = get_single_element_by_tag_name(xml.dom.minidom.parse(modalities_file), "modalities")
264 else:
265 self.modalities_dom = None
266
267
268
269 attrs = self.grammar_dom.getElementsByTagName("attr")
270
271 self.max_categories = None
272
273 for el in attrs:
274 name = el.getAttribute("name")
275 value = el.getAttribute("value")
276
277 if name == "max_categories":
278 self.max_categories = int(value)
279
280
281
282 self.chord_classes = {}
283 for entry in self.morph_dom.getElementsByTagName("class"):
284 chord_class = ChordClass.from_dom(entry)
285 self.chord_classes[chord_class.name] = chord_class
286
287
288
289
290
291
292 self.families = {}
293 self.inactive_families = []
294 for family in self.lexicon_dom.getElementsByTagName("family"):
295 fam = Family.from_dom(formalism, family)
296
297 if len(fam.entries) > 0:
298
299 if fam.pos in self.families:
300
301 self.families[fam.pos].append(fam)
302 else:
303
304 self.families[fam.pos] = [fam]
305 else:
306 self.inactive_families.append(fam.pos)
307
308
309
310 self.morphs = []
311 for entry in self.morph_dom.getElementsByTagName("entry"):
312 morph = MorphItem.from_dom(formalism,entry,self.chord_classes)
313 self.morphs.append(morph)
314
315
316 for morph in self.morphs:
317 if morph.pos not in self.families:
318 raise GrammarReadError, "morph item refers to undefined "\
319 "part-of-speech '%s': %s" % (morph.pos, morph.element.toxml())
320
321
322
323 if self.modalities_dom:
324 self.modality_tree = ModalityTree.from_dom(self.modalities_dom)
325 else:
326
327
328 self.modality_tree = ModalityTree([
329 ModalityTreeNode("",
330 [ModalityTreeNode("c")]) ])
331
332
333
334 self.rules = []
335
336 rule_block = get_single_element_by_tag_name(self.rules_dom, "rules")
337
338 for rule_tag in remove_unwanted_elements(rule_block.childNodes):
339 rulename = rule_tag.tagName
340 if rulename == "lexrules":
341
342 continue
343 if rulename not in self.formalism.rules:
344 raise GrammarReadError, "unknown rule '%s' (formalism "\
345 "defines: %s)" % (rulename, ", ".join(formalism.rules.keys()))
346 ruleclass = self.formalism.rules[rulename]
347
348 self.rules.append(ruleclass(modalities=self.modality_tree, grammar=self, **attrs_to_dict(rule_tag.attributes)))
349
350
351 self.unary_rules = []
352 self.binary_rules = []
353 for rule in self.rules:
354 if rule.arity == 1:
355 self.unary_rules.append(rule)
356 elif rule.arity == 2:
357 self.binary_rules.append(rule)
358
359
360 self.rules_by_name = {}
361 for rule in self.rules:
362 if rule.internal_name in self.rules_by_name:
363
364 raise GrammarReadError, "instantiated two rules with the same "\
365 "internal name: %s. Either the XML has mistakenly "\
366 "instantiated the same thing twice, or the rule class has "\
367 "failed to give different varieties of the rule different "\
368 "names" % rule.internal_name
369 self.rules_by_name[rule.internal_name] = rule
370
371
372
373 self.lexical_rules = []
374 lexrules_tag = get_single_element_by_tag_name(self.rules_dom, "lexrules", optional=True)
375 if lexrules_tag is not None:
376 for rule_tag in remove_unwanted_elements(lexrules_tag.childNodes):
377 rulename = rule_tag.tagName
378 if rulename not in self.formalism.rules:
379 raise GrammarReadError, "unknown lexical expansion "\
380 "rule '%s' (formalism defines: %s)" % \
381 (rulename, ", ".join(formalism.rules.keys()))
382 ruleclass = self.formalism.rules[rulename]
383 attrs = attrs_to_dict(rule_tag.attributes)
384
385
386 if "pos_suffix" in attrs:
387 pos_suffix = attrs["pos_suffix"]
388 del attrs["pos_suffix"]
389 else:
390 pos_suffix = "_Rep"
391
392 rule = ruleclass(modalities=self.modality_tree,
393 grammar=self,
394 **attrs)
395 rule.pos_suffix = pos_suffix
396
397 if rule.arity != 1:
398 raise "can only use unary rules as lexical "\
399 "expansions. Tried to use %s, which has arity "\
400 "%d." % (rulename, rule.arity)
401 self.lexical_rules.append(rule)
402
403 for rule in self.lexical_rules:
404 for fam in sum(self.families.values(), []):
405 for entry in fam.entries:
406
407 new_signs = rule.apply_rule([entry.sign])
408 if new_signs is not None and len(new_signs) > 0:
409
410 new_pos = "%s%s" % (fam.pos, rule.pos_suffix)
411 new_entries = [EntriesItem(self.formalism, "Expanded", new_sign) \
412 for new_sign in new_signs]
413 new_family = Family(self.formalism,
414 new_pos,
415 new_pos,
416 new_entries,
417 chordfn=fam.chordfn,
418 expanded=rule.internal_name)
419 self.families.setdefault(new_pos, []).append(new_family)
420
421
422 for morph in [m for m in self.morphs if m.pos == fam.pos]:
423 self.morphs.append(
424 MorphItem(
425 self.formalism,
426 copy.deepcopy(morph.words),
427 new_pos,
428 optional_minor=morph.optional_minor,
429 chord_class=morph.chord_class))
430
431
432
433 self.morph_items = {}
434 for morph in self.morphs:
435
436 if not morph.pos in self.inactive_families:
437
438 for word in morph.words:
439
440 if word in self.morph_items:
441
442 self.morph_items[word].append(morph)
443 else:
444
445 self.morph_items[word] = [morph]
446
447
448
449 equiv_map_el = get_single_element_by_tag_name(self.morph_dom, "equivmap", optional=True)
450 if equiv_map_el is not None:
451 self.equiv_map = EquivalenceMap.from_dom(formalism,
452 equiv_map_el,
453 self.chord_classes,
454 self.morphs)
455 else:
456 self.equiv_map = EquivalenceMap()
457
458
459
460 self.midi_families = {}
461 for pos,fams in self.families.items():
462 new_fams = []
463 for fam in fams:
464
465
466 if fam.expanded is not None and fam.chordfn != "T":
467 continue
468 new_fams.append(fam)
469 if new_fams:
470
471
472 if pos in self.equiv_map:
473 continue
474 self.midi_families[pos] = new_fams
475
476
477 logger.debug( "Read the following information from the grammar:")
478 logger.debug( "Morphology:")
479 logger.debug("\n".join(["%s: %s" % (word, ", ".join(["%s" % item.pos for item in items])) \
480 for word,items in self.morph_items.items()]))
481 logger.debug("Lexicon:")
482 logger.debug("\n".join([", ".join(["%s" % initem for initem in item]) \
483 for item in self.families.values()]))
484 logger.debug("Rules:")
485 logger.debug("\n".join([" %s" % item for item in self.rules]))
486 logger.debug("Lexical expansion rules:")
487 logger.debug("\n".join([" %s" % item for item in self.lexical_rules]))
488 logger.debug("Modalities:")
489 logger.debug("%s" % self.modality_tree)
490 if len(self.literal_functions):
491 logger.debug("Literal functions:")
492 logger.debug("\n".join([" %s: %s" % (name,val) for (name,val) in self.literal_functions.items()]))
493
494
496 """
497 Given a word string, returns a list of the possible signs
498 (as CCGSigns) that the grammar can assign to it.
499 word may also be a Chord object.
500 For now, this assumes that the input is a single chord in
501 roman numeral notation and that spelling issues have already
502 been resolved (e.g. that 6s have been removed).
503
504 If tags is given it should be a list of strings. Signs will be
505 restricted to those whose entry's tag name/POS is in the list.
506
507 If you need to get an instantiated category from a lexical entry,
508 use the methods on L{EntriesItem} directly, or L{get_signs_for_tag}.
509
510 """
511 if isinstance(word, Chord):
512 chord = word.to_db_mirror()
513 elif isinstance(word, basestring):
514 chord = Chord.from_name(word, permissive=True).to_db_mirror()
515 elif isinstance(word, DbChord):
516 chord = word
517 else:
518 raise GrammarLookupError, "Tried to get signs for a word of type "\
519 "'%s': %s" % (type(word), word)
520
521 chord_lookup = "X%s" % chord.type
522
523
524 if not chord_lookup in self.morph_items:
525
526 raise GrammarLookupError, "The word \"%s\" was not found in the "\
527 "lexicon. (Looked up %s in %s)" \
528 % (word, chord_lookup,
529 ",".join(["%s" % item for item in self.morph_items.keys()]))
530
531 morphs = self.morph_items[chord_lookup]
532
533
534 if tags is not None:
535 morphs = [m for m in morphs if m.pos in tags]
536
537
538 category_list = []
539 for morph in morphs:
540
541 if not morph.pos in self.families:
542 raise GrammarLookupError, "There is no family in the lexicon "\
543 "for the POS %s." % morph.pos
544 for family in self.families[morph.pos]:
545
546 for entry in family.entries:
547 sign = entry.sign.copy()
548 sign.tag = entry.tag_name
549 features = {
550 'root' : chord.root,
551 'morph' : morph
552 }
553 if extra_features is not None:
554 features.update(extra_features)
555 sign.apply_lexical_features(features)
556 category_list.append(sign)
557
558 return category_list
559
561 """
562 Returns a sign that can be assigned to the given word and that
563 has the given tag. If the word cannot have that tag, returns
564 None.
565 """
566 possibles = self.get_signs_for_word(word, tags=[tag], extra_features=extra_features)
567 if len(possibles) == 0:
568 return None
569 else:
570 return possibles[0]
571
573 """
574 Instantiates all the signs associated with a pos tag, using the given
575 dictionary of lexical features.
576
577 """
578
579 entries = sum([fam.entries for fam in self.families[tag]], [])
580
581 return [entry.get_lexical_sign(features, self) for entry in entries]
582
584 """
585 Gets all the tags recognised by the grammar. The tags are
586 returned as indices in a dict to the entry they represent.
587 Note that these tags are unique identifiers of lexical items,
588 not pos tags.
589 If you want entries indexed by POS tags, just use
590 grammar.families.
591 """
592 families = self.families
593 entries = {}
594
595 for pos in sorted(families.keys()):
596 for fam in families[pos]:
597 for entry in fam.entries:
598 entries[entry.tag_name] = entry
599 return entries
600 entries_by_tag = property(_get_entries_by_tag)
601
609 pos_tags = property(_get_pos_tags)
610
612 """
613 Given a POS tag (roughly denoting a category), returns the
614 function of the chord that is implicit in this interpretation.
615 If the tag is not in the grammar or a function is not given
616 for this tag in the lexicon, returns None.
617
618 """
619 if tag in self.families:
620 return self.families[tag][0].chordfn
621 else:
622 return None
623
631 tags_by_function = property(_get_tags_by_function)
632
633
634
635
636
637
638 -class Family(object):
639 """
640 A lexical family.
641
642 """
643 - def __init__(self, formalism, name, pos, entries, chordfn=None, expanded=None):
644 """
645 @type expanded: string or None
646 @param expanded: if the family is generated by a lexical expansion
647 rule, should contain the name of the rule that generated it.
648 Otherwise None
649
650 """
651 self.formalism = formalism
652 self.name = name
653 self.pos = pos
654 self.chordfn = chordfn
655 self.entries = entries
656 for entry in self.entries:
657 entry.family = self
658 self.expanded = expanded
659
661 return "<Family: \"%s\", \"%s\">%s</Family>" % (self.name, self.pos, \
662 "".join(["%s" % entry for entry in self.entries]))
663
664 @staticmethod
666 """
667 Builds a Family instance from a DOM element read in from the
668 XML grammar definition.
669
670 """
671 name = element.attributes.getNamedItem("name").value
672 pos = element.attributes.getNamedItem("pos").value
673
674 chordfn = element.attributes.getNamedItem("chordfn")
675 if chordfn is None:
676
677 chordfn = None
678 else:
679 chordfn = chordfn.value
680
681
682
683 entries = []
684
685 for entry in element.getElementsByTagName("entry"):
686 entry = EntriesItem.from_dom(formalism,entry)
687
688 if entry.active:
689 entries.append(entry)
690
691 return Family(formalism,
692 name,
693 pos,
694 entries,
695 chordfn=chordfn)
696
698 """
699 A morphological item - a word. Stores the word and the POS.
700 Words are stored as strings, unlike in OpenCCG. We don't need all the
701 extra information about words that OpenCCG holds.
702
703 The word field stores a list of strings, defining all the words
704 represented by this morph item. In the case of a
705 C{<entry pos="POS" word="WORD"/>} item, this is a single word,
706 but in the case of a C{<entry pos="POS" class="CLASS"/>}
707 item, this is all the words contained in the class CLASS.
708
709 The argument classes should be a dictionary mapping class names to
710 L{ChordClass} objects, for all the classes that have been
711 read in for the grammar.
712
713 """
714 - def __init__(self, formalism, words, pos, optional_minor=None,
715 chord_class=None, mark_class=True):
716 self.formalism = formalism
717 self.words = words
718 self.pos = pos
719 self.optional_minor = optional_minor
720 self.chord_class = chord_class
721 if mark_class:
722
723 if chord_class is not None:
724 chord_class.used = True
725
727 return "<MorphItem: %s>" % self.desc
728
731
733 if self.chord_class is not None:
734 return "cls %s := %s" % (self.chord_class, self.pos)
735 else:
736 return "%s := %s" % \
737 (",".join(["%s" % word for word in self.words]), self.pos)
738 desc = property(__get_desc)
739
740 @staticmethod
741 - def from_dom(formalism, element, classes, mark_class=True):
742 """
743 Builds a MorphItem instance from a DOM XML specification.
744
745 """
746 word_attr = element.attributes.getNamedItem("word")
747 if word_attr is not None:
748 words = [word_attr.value]
749 chord_class = None
750 else:
751
752 class_attr = element.attributes.getNamedItem("class")
753 if class_attr is None:
754 raise GrammarReadError, "Morph item has no word or class attribute"
755 class_name = class_attr.value
756 if not class_name in classes:
757 raise GrammarReadError, "Referenced word class has not been defined"
758
759
760 chord_class = classes[class_name]
761 words = copy.deepcopy(chord_class.words)
762
763 pos = element.attributes.getNamedItem("pos").value
764
765
766 optmin_element = element.attributes.getNamedItem("optional_minor")
767 if optmin_element is None:
768
769 optional_minor = None
770 else:
771 optmin_val = optmin_element.value
772 if optmin_val == "minor":
773
774 optional_minor = True
775 elif optmin_val == "major":
776
777 optional_minor = False
778 else:
779
780 logger.warning("An invalid value was found for the optional_"\
781 "minors attribute: %s. It must be \"major\" or "\
782 "\"minor\"" % optmin_val)
783 optional_minor = None
784 return MorphItem(formalism, words, pos, optional_minor=optional_minor, \
785 chord_class=chord_class, mark_class=mark_class)
786
789 """
790 Not yet implemented. Might be needed
791 """
792 pass
793
796 - def __init__(self, formalism, name, sign, active=True, family=None):
802
809
811 return "<Entry: %s>" % self.category
812
815
817 """ The unique tag name used for this lexical entry. """
818 if self.family is None:
819 return
820 elif len(self.family.entries) == 1:
821 return "%s" % self.family.name
822 else:
823 return "%s%s" % (self.family.name, self.name)
824 tag_name = property(_get_tag_name)
825
826 @property
828 """
829 Alias for C{sign} attribute, for backwards compatibility.
830 """
831 return self.sign
832
834 """
835 The sign stored in the EntriesItem is an abstraction of the lexical
836 signs, or a lexical schema, that may be instantiated. This method
837 produces a specialised lexical sign using the lexical information.
838
839 @type features: dict
840 @param features: lexical features to be passed directly to the sign's
841 C{apply_lexical_features} method.
842
843 """
844
845 sign = self.sign.copy()
846 sign.tag = self.tag_name
847
848 sign.apply_lexical_features(features)
849 return sign
850
851 @staticmethod
853 name = element.attributes.getNamedItem("name").value
854 active_el = element.attributes.getNamedItem("active")
855 if active_el is not None:
856 active = (active_el.value == "true")
857 else:
858 active = True
859 sign = formalism.lexicon_builder(element)
860
861 return EntriesItem(formalism, name, sign, active=active)
862
864 """
865 Representation of a chord class. Primarily, this links morph entries to
866 words.
867
868 """
869 - def __init__(self, name, words, notes=[]):
870 self.name = name
871 self.words = words
872 self.notes = notes
873 self.used = False
874
875 @staticmethod
877
878 name = element.attributes.getNamedItem("name").value
879 word_string = element.attributes.getNamedItem("words").value
880 words = word_string.split()
881
882 notes_el = element.attributes.getNamedItem("notes")
883 if notes_el is None:
884 notes = []
885 else:
886 notes = [int(note) for note in notes_el.value.split()]
887 return ChordClass(name, words, notes=notes)
888
891
893 return "<ChordClass %s>" % self.name
894
896 """
897 A mapping from some of the morph entries to others via a root change.
898
899 These define entries that can be considered equivalent to another
900 entry given a change of root. These exist to deal with inversions
901 that show up in chord sequences as different chords.
902
903 E.g., a diminished chord may be written as if any of its notes
904 are the root, but this decision is made mainly on the basis of
905 what is easiest to read. Categories must be included to handle
906 each case for the chord grammar, but only one is required for a
907 model that can recognise inversions itself, such as a MIDI model.
908
909 Here we tell such models how to construct a smaller set of
910 categories, so that they can understand annotated chord data.
911
912 """
915
916 @staticmethod
917 - def from_dom(formalism, element, classes, morphs):
936
937 -class EquivalenceEntry(object):
938 """
939 Like a L{MorphItem}. Represents the target in a mapping from one morph
940 entry to another. For now this only works with chord class entries,
941 not words.
942
943 """
944 - def __init__(self, target, root):
945 self.target = target
946 self.root = root
947
949 return "<Equiv: %d, %s>" % (self.root, self.target.desc)
950
951 - def __repr__(self):
953
954 @staticmethod
955 - def from_dom(formalism, element, classes, morphs):
956
957 morph = MorphItem.from_dom(formalism, element, classes, mark_class=False)
958
959 root = int(element.attributes.getNamedItem("root").value)
960
961
962 if morph.chord_class is None:
963 raise GrammarReadError, "equivalence mappings can only currently "\
964 "map to morph entries that use chord classes"
965
966
967 for old_morph in morphs:
968 if old_morph.chord_class == morph.chord_class and \
969 old_morph.pos == morph.pos:
970 target = old_morph
971 break
972 else:
973
974 raise GrammarReadError, "could not find a morph entry to use as "\
975 "equivalence target matching pos=%s and chord_class=%s" % \
976 (morph.pos, morph.chord_class)
977 return EquivalenceEntry(target, root)
978
980 """ Returns a list of all valid grammar names. """
981 dirs = [d for d in os.listdir(settings.GRAMMAR_DATA_DIR) if not d.startswith(".")]
982 grammars = []
983
984 for name in dirs:
985 dirname = os.path.abspath(os.path.join(settings.GRAMMAR_DATA_DIR, name))
986 if not os.path.exists(dirname):
987 continue
988 if not os.path.isdir(dirname):
989 continue
990 if not os.path.exists(os.path.join(dirname, "grammar.xml")):
991 continue
992 grammars.append(name)
993 return grammars
994
995
996 _loaded_grammars = {}
999 """
1000 Returns an instance of L{Grammar} for the named grammar.
1001
1002 This is like instantiating L{Grammar} with the grammar with the name
1003 as an argument, but caches loaded grammars. If the named grammar has
1004 been previously loaded, the same instance will be returned again.
1005
1006 If you want to force a new instance, use C{Grammar(name)}. However,
1007 most of the time there's no need, since Grammar is essentially a read-only
1008 data structure.
1009
1010 """
1011 if name is None:
1012 name = settings.DEFAULT_GRAMMAR
1013 if name not in _loaded_grammars:
1014 _loaded_grammars[name] = Grammar(name)
1015 return _loaded_grammars[name]
1016
1018 """
1019 Thrown if there's a problem while reading the grammar description
1020 from the XML file. This will usually be because of missing elements
1021 or some such thing.
1022 """
1023 pass
1024
1026 """
1027 Raised if there are problems consulting the grammar.
1028 """
1029 pass
1030