Plex 로 Wiki Page Parser 를 만들던중. Plex 는 아주 훌륭한 readability 의 lexical analyzer code 를 만들도록 도와준다. === Plex Example : Wiki Parser === 현재 PyKi라는, [1002]가 개인적으로 만들어서 사용중인 위키에서의 parser 클래스 중 일부 코드이다. {{{~cpp from Plex import * import cStringIO, StringIO class Parser: def __init__(self, anInterWikiMap={}, aScriptName='', aMacros={}): self.interWikiMap = anInterWikiMap self.scriptName = aScriptName self.macros = aMacros def makeToStream(self, aText): return cStringIO.StringIO(aText) def parse(self, aText): stream = self.makeToStream(aText) return WikiParser(stream, self.interWikiMap,self.scriptName, self.macros).linkedLine() class WikiParser(Scanner): def repl_bold(self, aText): self.bold = not self.bold return ("","")[not self.bold] def repl_italic(self, aText): self.italic = not self.italic return ("","")[not self.italic] def repl_boldAndItalic(self, aText): self.italic = not self.italic return ("","")[not self.italic] def repl_ruler(self, aText): return "
\n" def repl_enter(self, aText): return "
\n" def getLinkStr(self, anUrl): return "%(url)s" % {'url':anUrl} def getImageStr(self, anUrl): return "" % {'url':anUrl} def isImage(self, aFileUrl): imgExtensions = ("jpg", "png", "gif", "jpeg") extension = aFileUrl[aFileUrl.rfind(".")+1:] return extension in imgExtensions def repl_url(self, aText): if self.isImage(aText): return self.getImageStr(aText) return self.getLinkStr(aText) def repl_interwiki(self, aText): wikiMapName,pageName = self.text.split(":") if self.interWikiMap.has_key(wikiMapName): url = self.interWikiMap[wikiMapName] imageTag = "" formatText = "%(imageTag)s%(pageName)s" else: url = self.scriptName+"/InterMap" imageTag = "Bad:%s"%(wikiMapName) formatText="%(imageTag)s%(pageName)s" return formatText % {'url':url, 'pageName':pageName, 'imageTag':imageTag} def repl_pagelink(self, aText): if aText == "NonExistPage": return "%(pageName)s" % {'scriptName':self.scriptName, 'pageName':aText} return "%(pageName)s" \ % {'scriptName':self.scriptName, 'pageName':aText} def repl_pagelinkUsingUnderbar(self, aText): pageName = aText[:-1] return "%(pageName)s" % {'scriptName':self.scriptName, 'pageName':pageName} def repl_macro(self, aText): class NoneMacro: def execute(self): return '' macroName = aText[2:-2] macro=self.macros.get(macroName) or NoneMacro() return macro.execute() def repl_normalString(self, aText): return aText # for auto link urlHeader = Str("http://") stringUntilSpace = Rep(AnyBut(" ")) url = urlHeader + stringUntilSpace upperCase = Range('AZ') lowerCase = Range('az') upperCaseSequence = Rep1(upperCase) lowerCaseSequence = Rep1(lowerCase) alphabetSequence = Rep1(upperCaseSequence | lowerCaseSequence) interwikiDelim = Str(":") stringUntilSpaceOrCommaOrRest = Rep1(AnyBut(" .,")) interwiki = alphabetSequence + interwikiDelim + stringUntilSpaceOrCommaOrRest pagelinkUsingCamelWord = upperCase + Rep(lowerCase) + Rep1(upperCaseSequence + lowerCaseSequence) underbarForLink = Str('_') pagelinkUsingUnderbar = Rep1(AnyBut('{\n_ ')) + underbarForLink macro = Str('[[') + Rep1(AnyBut(']]')) + Str(']]') space = Str(" ") # for tag bold = Str("'''") italic = Str("''") boldanditalic = bold + italic htmllefttag = Str("<") htmlrighttag = Str(">") htmlandtag = Str("&") enterCode = Str("\n") ruler = Str("----") + enterCode rawTextStart=Str("{{{~cpp ") rawTextEnd=Str("} } }") def repl_rawTextStart(self, aText): self.begin("rawtext") return "
"

    def repl_rawTextEnd(self, aText):
        self.begin("")
        return "
" lexicon = Lexicon([ (rawTextStart, repl_rawTextStart), State('rawtext', [ (rawTextEnd, repl_rawTextEnd), (AnyChar, repl_normalString), ]), (italic, repl_italic), (bold, repl_bold), (boldanditalic, repl_boldAndItalic), (htmllefttag, "<"), (htmlrighttag, ">"), (htmlandtag, "&"), (url, repl_url), (interwiki, repl_interwiki), (pagelinkUsingCamelWord, repl_pagelink), (ruler, repl_ruler), (enterCode, repl_enter), (pagelinkUsingUnderbar, repl_pagelinkUsingUnderbar), (macro, repl_macro), (AnyChar | space, repl_normalString),]) def __init__(self, aStream, anInterWikiMap={}, aScriptName='pyki.cgi', aMacroList={}): Scanner.__init__(self, self.lexicon, aStream) self.interWikiMap=anInterWikiMap self.scriptName = aScriptName self.macros = aMacroList self.bold = False self.italic = False def linkedLine(self): writer = StringIO.StringIO("") while True: token = self.read() if token[0] is None: break writer.write(token[0]) return writer.getvalue() }}} === 개발중 이슈 === 처음에는 Wiki 에서 Tag 에 대해 Tagger 클래스를 만들고, link 를 걸어주는 부분에 대해 AutoLinker 를, Macro 에는 MacroApplyer 를 각각 만들어주었다. 그러다가 문제가 생겼는데, 태그중에 그 영향력이 겹치는 부분이 생겨나게 된 것이다. 즉, 예를 든다면 Macro 의 경우 CamelWord 로 이름지어지기도 하는데, 이는 AutoLinker 의 apply 를 거치면서 archor 태그가 붙어버리는 것이다. 해결방법 : 두가지인데, 하나는 AutoLinker 에서 Macro 관련 태그시 무시하고 지나가는 방법이고 하나는 AutoLinker 와 MacroApplyer를 통합하는 방법이다. 전자의 경우 각각의 Class Responsibility 들을 유지한다는 장점이 있지만, AutoLinker 에서 원래 생각치 않았던 한가지 일을 더 해야 한다는 점이 있겠다. 후자의 경우 클래스가 커진다는 단점이 있지만, 의도한 lexical 들만 표현된다는 점과 1 pass 로 파싱이 같이 이루어질 수 있다는 장점이 있다. 결국은 후자를 선택하였다. 근데, 그러면서 이번엔 Tagger 와 AutoLinker 양쪽에 영향력을 미칠 거리가 생겼는데, 바로 텍스트를 그대로 보여주는 태그부분이다. 그러나......~ 후자로 수정하는데 40분도 안걸리다.; 작업으로 본다면 Parser 두개의 lexicon 을 합치는 작업임에도 불구하고, 그 안정성도 보장받으면서 parser 에서 line 단위 자르기 부분까지 수정하였다. 매 번 수정할때마다 테스트를 돌리면서 확인했기 때문에 그 결과가 보장이 되었다. Text Processing 에서 이러한 부분에 대한 TDD의 파워는 정말 크다란 생각이 든다. ---- [Plex]