# -*- coding: utf-8 -*- ############################################################################### # Name: synxml.py # # Purpose: Syntax Mode Xml Interface # # Author: Cody Precord # # Copyright: (c) 2009 Cody Precord # # License: wxWindows License # ############################################################################### """ Interface for extending language support through the use of xml files @summary: EditraXml Implementation """ xml_spec = """ if else elif for while in str len setattr getattr """ __author__ = "Cody Precord " __svnid__ = "$Id: synxml.py 68814 2011-08-21 17:10:03Z CJP $" __revision__ = "$Revision: 68814 $" #----------------------------------------------------------------------------# # Imports import os from xml import sax # Workaround for building packages when wx is not available try: import wx.stc as stc except ImportError: pass #----------------------------------------------------------------------------# # Tag Definitions EXML_START = u"editra" EXML_SYNTAX = u"syntax" EXML_KEYWORDLIST = u"keywordlist" EXML_KEYWORDS = u"keywords" EXML_SYNSPECLIST = u"syntaxspeclist" EXML_SYNTAXSPEC = u"syntaxspec" EXML_PROPERTYLIST = u"propertylist" EXML_PROPERTY = u"property" EXML_COMMENTPAT = u"commentpattern" EXML_FEATURELIST = u"featurelist" EXML_FEATURE = u"feature" EXML_ASSOCIATIONS = u"associations" # Attributes EXML_ENABLE = u"enable" EXML_LANG = u"language" EXML_LEXER = u"lexer" EXML_METHOD = u"method" EXML_SOURCE = u"source" EXML_TAG = u"tag" EXML_VALUE = u"value" EXML_VERSION = u"version" EXML_NAME = u"name" EXML_ID = u"id" #----------------------------------------------------------------------------# class EditraXml(sax.ContentHandler): """Base class for all Editra syntax xml objects""" def __init__(self, path=None): sax.ContentHandler.__init__(self) # Attributes self.name = u'' # Tag name self.level = 0 # Set the level of the element self.indent = 3 # Indentation self.path = path self._ok = False # Did the object load correctly self._context = None # Current parse context self._reg_handler = dict() # Registered parse handlers def __eq__(self, other): return self.GetXml() == other.GetXml() def __str__(self): return self.GetXml() #---- Internal Parser Api ----# def startElement(self, name, attrs): if self._context is not None: # Delegate to current context self._context.startElement(name, attrs) elif name in self._reg_handler: self._context = self._reg_handler.get(name) self._context.startElement(name, attrs) else: # Unknown tag # raise? pass def endElement(self, name): if self._context is not None: if self._context.Name == name: self._context = None else: # Delegate to registered handler self._context.endElement(name) else: # Unknown tag ending pass def characters(self, chars): if not chars.isspace(): if self._context is not None: self._context.characters(chars) else: # No current context or context is self pass def GetXml(self): """Get the xml representation of this object @return: string """ ident = self.GetIndentationStr() xml = ident + self.GetStartTag() xml += (self.GetSubElements() + os.linesep) xml += (ident + self.GetEndTag()) return xml def GetSubElements(self): """Get the sub elements @return: string """ xml = u'' for handler in self.GetHandlers(): handler.SetLevel(self.Level + 1) xml += (os.linesep + handler.GetXml()) return xml def GetStartTag(self): """Get the opening tag @return: string """ return u"<%s>" % self.name def GetEndTag(self): """Get the closing tag @return: string """ return u"" % self.name def LoadFromDisk(self): """Load the object from from disk @precondition: path has been set @return: bool @todo: set error state on failed loads """ assert self.path is not None, "Must SetPath before calling Load" try: sax.parse(self.path, self) except (sax.SAXException, OSError, IOError, UnicodeDecodeError): self._ok = False return False else: self._ok = True return True def LoadFromString(self, txt): """Load and initialize the object from an xml string @param txt: string """ sax.parseString(txt, self) #---- External Api ----# @property def Context(self): return self._context @property def Indentation(self): return self.indent @property def Level(self): return self.level @property def Name(self): return self.name @property def Ok(self): return self.IsOk() def GetHandler(self, tag): """Get the handler associated with the given tag @param tag: string @return: EditraXml object or None """ return self._reg_handler.get(tag, None) def GetHandlers(self): """Get all the handlers registered with this element @return: list of EditraXml objects """ return self._reg_handler.values() def GetIndentation(self): """Get the indentation string @return: int """ return self.indent def GetIndentationStr(self): """Get the indentation string taking level into consideration @return: string """ return (self.indent * u" ") * self.level def GetLevel(self): """Get the level of this element @return: int """ return self.level def GetName(self): """Get the tag name for the handler @return: string """ return self.name def GetPath(self): """Get the xml files path @return: string """ return self.path def IsOk(self): """Did the object load from file correctly? return: bool """ return self._ok def RegisterHandler(self, handler): """Register a handler for a tag. Parsing will be delegated to the the registered handler until its end tag is encountered. @param handler: EditraXml instance """ tag = handler.GetName() assert tag not in self._reg_handler, "%s already registered!" % tag handler.SetLevel(self.Level + 1) self._reg_handler[tag] = handler def SetIndentation(self, indent): """Set the indentation level @param indent: int """ self.indent = indent def SetLevel(self, level): """Set the level of this element @param level: int """ self.level = level def SetName(self, tag): """Set this handlers tag name used for identifying the open and end tags. @param tag: string """ self.name = tag def SetPath(self, path): """Set the path to load this element from @param path: path """ self.path = path #----------------------------------------------------------------------------# class SyntaxModeHandler(EditraXml): """Main Xml interface to extending filetype handling support""" def __init__(self, path=None): EditraXml.__init__(self, path) # Attributes self._start = False self._version = 0 self.syntax = Syntax() # Setup self.SetName(EXML_START) self.SetIndentation(3) self.RegisterHandler(self.syntax) def startElement(self, name, attrs): if self._start: EditraXml.startElement(self, name, attrs) elif name == EXML_START: self._version = int(attrs.get(EXML_VERSION, 0)) self._start = True def endElement(self, name): if name == EXML_START: self._start = False else: EditraXml.endElement(self, name) def GetStartTag(self): return u"<%s version=\"%s\">" % (self.GetName(), self.Version) #---- Get External Api ----# # Properties @property def CommentPattern(self): return self.GetCommentPattern() @property def FileExtensions(self): return self.GetFileExtensions() @property def Keywords(self): return self.GetKeywords() @property def LangId(self): return self.GetLangId() @property def Lexer(self): return self.GetLexer() @property def Properties(self): return self.GetProperties() @property def SyntaxSpec(self): return self.GetSyntaxSpec() @property def Version(self): return self._version # Getters def GetCommentPattern(self): """Get the comment pattern list @return: list of strings """ return self.syntax.GetCommentPattern() def GetFileExtensions(self): """Get the list of associated file extensions @return: list of strings """ return self.syntax.GetFileExtensions() def GetKeywords(self): """Get the keyword list @return: list of tuples [(idx, ['word', 'word2',]),] """ kwxml = self.syntax.GetKeywordXml() return kwxml.GetKeywords() def GetFeatureFromXml(self, fet): """Get the callable associated with the given feature @param fet: string @return: string """ fetxml = self.syntax.GetFeatureXml() return fetxml.GetFeature(fet) def GetSyntaxSpec(self): """Get the syntax spec @return: list of tuples [(style_id, "style_tag")] """ spxml = self.syntax.GetSyntaxSpecXml() return spxml.GetStyleSpecs() def GetLangId(self): """Get the language id string @return: str "ID_LANG_" """ return self.syntax.GetLangId() def GetLanguage(self): """Get the language name string @return: string """ return self.syntax.GetLanguage() def GetLexer(self): """Get the lexer id @return: wx.stc.STC_LEX_ """ return self.syntax.GetLexer() def GetProperties(self): """Get the property defs @return: list of tuples [("fold", "1"),] """ propxml = self.syntax.GetPropertiesXml() return propxml.GetProperties() #----------------------------------------------------------------------------# class Syntax(EditraXml): """Syntax definition for initializing a Scintilla Lexer""" def __init__(self): EditraXml.__init__(self) # Attributes self.language = u"Plain Text" self.langid = u"ID_LANG_TXT" self.lexstr = u"STC_LEX_NULL" self.lexer = stc.STC_LEX_NULL self.file_ext = list() # Sub Xml Objects self.keywords = KeywordList() self.synspec = SyntaxSpecList() self.props = PropertyList() self.features = FeatureList() self.comment = list() # Setup self.SetName(EXML_SYNTAX) self.RegisterHandler(self.keywords) self.RegisterHandler(self.synspec) self.RegisterHandler(self.props) self.RegisterHandler(self.features) def startElement(self, name, attrs): """Parse the Syntax Xml""" if name == EXML_COMMENTPAT: val = attrs.get(EXML_VALUE, '') tmp = val.split() # Trailing space may be significant for some comments if len(tmp) == 1: comment = [val,] else: comment = tmp self.comment = comment elif name == EXML_ASSOCIATIONS: self.file_ext = attrs.get(EXML_VALUE, '').split() elif name == self.Name: lang = attrs.get(EXML_LANG, u"Plain Text") langid = attrs.get(EXML_ID, u"ID_LANG_TXT") assert langid.startswith(u"ID_LANG_"), "id must start with ID_LANG_" lexer = attrs.get(EXML_LEXER, 'STC_LEX_NULL') lexval = getattr(stc, lexer, None) assert lexval is not None, "Invalid Lexer: %s" % lexer self.language = lang self.langid = langid self.lexstr = lexer self.lexer = lexval else: EditraXml.startElement(self, name, attrs) def GetStartTag(self): """Get the syntax opening tag and attributes""" return u"<%s %s=\"%s\" %s=\"%s\" %s=\"%s\">" % (self.Name, EXML_LANG, self.language, EXML_LEXER, self.lexstr, EXML_ID, self.langid) def GetSubElements(self): """Get the SubElements xml string @return: string """ xml = EditraXml.GetSubElements(self) ident = self.GetIndentationStr() + (self.Indentation * u" ") xml += os.linesep cpat = u" ".join(self.GetCommentPattern()) comment = u"<%s %s=\"%s\"/>" % (EXML_COMMENTPAT, EXML_VALUE, cpat.strip()) xml += os.linesep xml += (ident + comment) xml += os.linesep fileext = u"<%s %s=\"%s\"/>" % (EXML_ASSOCIATIONS, EXML_VALUE, u" ".join(self.file_ext)) xml += (ident + fileext) return xml #---- External Api ----# def GetCommentPattern(self): """Get the comment pattern @return: list of strings ["/*", "*/"] """ return self.comment def GetFeatureXml(self): """Get the FeatureList xml object""" return self.features def GetFileExtensions(self): """Get the list of associated file extensions""" return self.file_ext def GetKeywordXml(self): """Get the Keyword Xml object""" return self.keywords def GetLanguage(self): """Get the language description/name @return: string """ return self.language def GetLangId(self): """Get the language id @return: string """ return self.langid def GetLexer(self): """Get the lexer to use for this language @return: stc.STC_LEX_FOO """ return self.lexer def GetSyntaxSpecXml(self): """Get the Syntax Spec Xml object""" return self.synspec def GetPropertiesXml(self): """Get the properties xml object""" return self.props #----------------------------------------------------------------------------# class KeywordList(EditraXml): """Container object for all keyword sets""" def __init__(self): EditraXml.__init__(self) # Attributes self._current = None self._keywords = dict() # { index : word_list } # Setup self.SetName(EXML_KEYWORDLIST) def startElement(self, name, attrs): if name == EXML_KEYWORDS: idx = attrs.get(EXML_VALUE, None) assert idx is not None, "value attribute not set" idx = int(idx) assert idx not in self._keywords, "Duplicate index set %d" % idx self._keywords[idx] = list() self._current = self._keywords[idx] else: pass def endElement(self, name): if name == EXML_KEYWORDS: self._current = None def characters(self, chars): chars = chars.strip().split() if self._current is not None: if len(chars): self._current.extend(chars) def GetSubElements(self): """Get the keyword list elements""" xml = u"" tag = u"<%s %s=" % (EXML_KEYWORDS, EXML_VALUE) tag += "\"%s\">" end = u"" % EXML_KEYWORDS ident = self.GetIndentationStr() + (self.Indentation * u" ") for key in sorted(self._keywords.keys()): xml += os.linesep + ident xml += (tag % key) xml += os.linesep + ident words = (self.Indentation * u" ") + u" ".join(self._keywords[key]) xml += words xml += os.linesep + ident xml += end return xml #---- External Api ----# def GetKeywords(self): """Get the list of keyword strings @return: sorted list of tuples [(kw_idx, [word1, word2,])] """ keys = sorted(self._keywords.keys()) keywords = [ (idx, self._keywords[idx]) for idx in keys ] keywords.sort(key=lambda x: x[0]) return keywords def GetKeywordList(self, idx): """Get the list of keywords associated with the given index @return: list of strings """ return self._keywords.get(idx, None) #----------------------------------------------------------------------------# class SyntaxSpecList(EditraXml): """Container element for holding the syntax specification elements""" def __init__(self): EditraXml.__init__(self) # Attributes self._specs = list() # Setup self.SetName(EXML_SYNSPECLIST) def startElement(self, name, attrs): """Parse all syntaxspec elements in the list""" if name == EXML_SYNTAXSPEC: lid = attrs.get(EXML_VALUE, '') assert len(lid), "Style Id not specified" if lid.isdigit(): style_id = int(lid) else: # Scintilla Value style_id = getattr(stc, lid, None) assert style_id is not None, "Invalid STC Value: %s" % lid assert isinstance(style_id, int), "Invalid ID: %s" % lid self._specs.append((style_id, attrs.get(EXML_TAG, 'default_style'))) else: # Unknown Tag # Raise? pass def GetSubElements(self): """Get the xml for all the syntax spec elements""" xml = u"" tag = u"<%s %s=" % (EXML_SYNTAXSPEC, EXML_VALUE) tag += ("\"%s\" " + EXML_TAG + "=\"%s\"/>") ident = self.GetIndentationStr() + (self.Indentation * u" ") for spec in self._specs: xml += os.linesep + ident xml += (tag % spec) return xml #---- External Api ----# def GetStyleSpecs(self): """Get the list of keyword strings @return: list of tuples [(style_id, "style_tag"),] """ return self._specs #----------------------------------------------------------------------------# class PropertyList(EditraXml): """Container class for the syntax properties""" def __init__(self): EditraXml.__init__(self) # Attributes self.properties = list() # Setup self.SetName(EXML_PROPERTYLIST) def startElement(self, name, attrs): if name == EXML_PROPERTY: prop = attrs.get(EXML_VALUE, '') if prop: enable = attrs.get(EXML_ENABLE, '0') self.properties.append((prop, enable)) else: pass def GetSubElements(self): xml = u"" tag = u"<%s %s=" % (EXML_PROPERTY, EXML_VALUE) tag += ("\"%s\" " + EXML_ENABLE + "=\"%s\"/>") ident = self.GetIndentationStr() + (self.Indentation * u" ") for prop in self.properties: xml += os.linesep + ident xml += (tag % prop) return xml #---- External Api ----# def GetProperties(self): """Get the list of properties @return: list of tuples [("property", "1")] """ return self.properties #----------------------------------------------------------------------------# class FeatureList(EditraXml): """Container for all other misc syntax features. Currently Available Features: - AutoIndent - StyleText """ def __init__(self): EditraXml.__init__(self) # Attributes self._features = dict() # Setup self.SetName(EXML_FEATURELIST) def startElement(self, name, attrs): if name == EXML_FEATURE: meth = attrs.get(EXML_METHOD, None) assert meth is not None, "method not defined" mod = attrs.get(EXML_SOURCE, None) assert mod is not None, "source not defined" self._features[meth] = mod else: EditraXml.startElement(self, name, attrs) def GetSubElements(self): xml = u"" tag = u"<%s %s=" % (EXML_FEATURE, EXML_METHOD) tag += ("\"%s\" " + EXML_SOURCE + "=\"%s\"/>") ident = self.GetIndentationStr() + (self.Indentation * u" ") for feature in self._features.iteritems(): xml += (os.linesep + ident) xml += (tag % feature) return xml #---- External Api ----# def GetFeature(self, fet): """Get the callable feature by name @param fet: string (module name) """ feature = None src = self._features.get(fet, None) if src is not None: feature = src return feature #----------------------------------------------------------------------------# def LoadHandler(path): """Load and initialize a SyntaxModeHandler from an on disk xml config file @param path: path to an EditraXml file to load a handler from @return: SyntaxModeHandler instance """ synmode = SyntaxModeHandler(path) synmode.LoadFromDisk() return synmode