#-*- coding: utf-8 -*-
u"""Generator Kanta dla Pythona
Generuje pseudofilozofię opartą na gramatyce bezkontekstowej
Użycie: python kgp.py [options] [source]
Opcje:
-g ..., --grammar=... używa określonego pliku gramatyki lub adres URL
-h, --help wyświetla ten komunikat pomocy
-d wyświetla informacje debugowania podczas parsowania
Przykłady:
kgp.py generuje kilka akapitów z filozofią Kanta
kgp.py -g husserl.xml generuje kilka akapitów z filozofią Husserla
kpg.py "<xref id='paragraph'/>" generuje akapit Kanta
kgp.py template.xml czyta template.xml, aby określić, co ma generować
Ten program jest częścią książki "Zanurkuj w Pythonie", podręcznika
o Pythonie dla doświadczonych programistów. Najnowszą wersję można
znaleźć tu: http://pl.wikibooks.org/wiki/Zanurkuj_w_Pythonie.
Program ten został oparty na przykładach zawartych w książce
"Dive Into Python", a dostępnej stąd: http://www.diveintopython.org.
"""
__author__ = "Mark Pilgrim (mark@diveintopython.org)"
__version__ = "$Revision: 1.4 $"
__date__ = "$Date: 2004/05/05 21:57:19 $"
__copyright__ = "Copyright (c) 2001 Mark Pilgrim"
__license__ = "Python"
from xml.dom import minidom
import random
import toolbox
import sys
import getopt
_debug = 0
class NoSourceError(Exception): pass
class KantGenerator(object):
u"""generuje pseudofilozofię opartą na gramatyce bezkontekstowej"""
def __init__(self, grammar, source=None):
self.loadGrammar(grammar)
self.loadSource(source and source or self.getDefaultSource())
self.refresh()
def _load(self, source):
u"""wczytuje XML-owe źródłow wejścia, zwraca sparsowany dokument XML
- adres URL z plikiem XML ("http://diveintopython.org/kant.xml")
- nazwę lokalnego pliku XML ("~/diveintopython/common/py/kant.xml")
- standardowe wejście ("-")
- bieżący dokument XML w postaci łańcucha znaków
"""
sock = toolbox.openAnything(source)
xmldoc = minidom.parse(sock).documentElement
sock.close()
return xmldoc
def loadGrammar(self, grammar):
u"""wczytuje gramatykę bezkontekstową"""
self.grammar = self._load(grammar)
self.refs = {}
for ref in self.grammar.getElementsByTagName("ref"):
self.refs[ref.attributes["id"].value] = ref
def loadSource(self, source):
u"""wczytuje źródło source"""
self.source = self._load(source)
def getDefaultSource(self):
u"""zgaduje domyślne źródło bieżącej gramatyki
Domyślnym źródłem będzie jeden z <ref>-ów, do którego nic się
nie odwołuje. Może brzmi to skomplikowanie, ale tak naprawdę nie jest.
Przykład: Domyślnym źródłem dla kant.xml jest
"<ref id='section'/>", ponieważ 'section' jest jednym <ref>-em, który
nie jest nigdzie <xref>-em w gramatyce.
W wielu gramatykach, domyślne źródło będzie tworzyło
najdłuższe (i najbardziej interesujące) wyjście.
"""
xrefs = {}
for xref in self.grammar.getElementsByTagName("xref"):
xrefs[xref.attributes["id"].value] = 1
xrefs = xrefs.keys()
standaloneXrefs = [e for e in self.refs.keys() if e not in xrefs]
if not standaloneXrefs:
raise NoSourceError, "can't guess source, and no source specified"
return '<xref id="%s"/>' % random.choice(standaloneXrefs)
def reset(self):
u"""resetuje parser"""
self.pieces = []
self.capitalizeNextWord = 0
def refresh(self):
u"""resetuje bufor wyjściowy, ponownie parsuje cały plik źródłowy i zwraca wyjście
Ponieważ parsowanie dosyć dużo korzysta z przypadkowości, jest to
łatwy sposób, aby otrzymać nowe wyjście bez potrzeby ponownego wczytywania
pliku gramatyki.
"""
self.reset()
self.parse(self.source)
return self.output()
def output(self):
u"""wyjściowy, wygenerowany tekst"""
return "".join(self.pieces)
def randomChildElement(self, node):
u"""wybiera przypadkowy potomek węzła
Jest to użyteczna funkcja wykorzystywana w do_xref i do_choice.
"""
choices = [e for e in node.childNodes
if e.nodeType == e.ELEMENT_NODE]
chosen = random.choice(choices)
if _debug:
sys.stderr.write('%s available choices: %s\n' % \
(len(choices), [e.toxml() for e in choices]))
sys.stderr.write('Chosen: %s\n' % chosen.toxml())
return chosen
def parse(self, node):
u"""parsuje pojedynczy węzeł XML
Parsowany dokument XML (from minidom.parse) jest drzewem węzłów
złożonym z różnych typów. Każdy węzeł reprezentuje instancję
odpowiadającej jej klasy Pythona (Element dla znacznika, Text
dla danych tekstowych, Document dla dokumentu). Poniższe wyrażenie
konstruuje nazwę klasy opartej na typie węzła, który parsujemy
("parse_Element" dla węzła o typie Element,
"parse_Text" dla węzła o typie Text itp.), a następnie wywołuje te metody.
"""
parseMethod = getattr(self, "parse_%s" % node.__class__.__name__)
parseMethod(node)
def parse_Document(self, node):
u"""parsuje węzeł dokumentu
Węzeł dokument sam w sobie nie jest interesujący (przynajmnie dla nas), ale
jego jedyne dziecko, node.documentElement jest: jest głównym węzłem
gramatyki.
"""
self.parse(node.documentElement)
def parse_Text(self, node):
u"""parsuje węzeł tekstowy
Tekst węzła tekstowego jest zazwyczaj dodawany bez zmiany do wyjściowego bufora.
Jedynym wyjątkiem jest to, że <p class='sentence'> ustawia flagę, aby
pierwszą literę następnego słowa była wielka. Jeśli ta flaga jest ustawiona,
pierwszą literę tekstu robimy wielką i resetujemy tę flagę.
"""
text = node.data
if self.capitalizeNextWord:
self.pieces.append(text[0].upper())
self.pieces.append(text[1:])
self.capitalizeNextWord = 0
else:
self.pieces.append(text)
def parse_Element(self, node):
u"""parsuje element
XML-owy element odpowiada bieżącemu znacznikowi źródła:
<xref id='...'>, <p chance='...'>, <choice> itp.
Każdy typ elementu jest obsługiwany za pomocą odpowiedniej, własnej metody.
Podobnie jak to robiliśmy w parse(), konstruujemy nazwę metody
opartej na nazwie elementu ("do_xref" dla znacznika <xref> itp.), a potem
wywołujemy tę metodę.
"""
handlerMethod = getattr(self, "do_%s" % node.tagName)
handlerMethod(node)
def parse_Comment(self, node):
u"""parsuje komentarz
Gramatyka może zawierać komentarze XML, ale my je pominiemy
"""
pass
def do_xref(self, node):
u"""obsługuje znacznik <xref id='...'>
Znacznik <xref id='...'> jest odwołaniem do znacznika <ref id='...'>.
Znacznik <xref id='sentence'/> powoduje to, że zostaje wybrany w przypadkowy sposób
potomek znacznika <ref id='sentence'>.
"""
id = node.attributes["id"].value
self.parse(self.randomChildElement(self.refs[id]))
def do_p(self, node):
u"""obsługuje znacznik <p>
Znacznik <p> jest jądrem gramatyki. Może zawierać niemal
wszystko: tekst w dowolnej formie, znaczniki <choice>, znaczniki <xref>, a nawet
inne znaczniki <p>. Jeśli atrybut "class='sentence'" zostanie znaleziony, flaga
zostaje ustawiona i następne słowo będzie zapisane dużą literą. Jeśli zostanie
znaleziony atrybut "chance='X'", to mamy X% szansy, że znacznik zostanie wykorzystany
(i mamy (100-X)% szansy, że zostanie całkowicie pominięty)
"""
keys = node.attributes.keys()
if "class" in keys:
if node.attributes["class"].value == "sentence":
self.capitalizeNextWord = 1
if "chance" in keys:
chance = int(node.attributes["chance"].value)
doit = (chance > random.randrange(100))
else:
doit = 1
if doit:
for child in node.childNodes: self.parse(child)
def do_choice(self, node):
u"""obsługuje znacznik <choice>
Znacznik <choice> zawiera jeden lub więcej znaczników <p>. Jeden znacznik <p>
zostaje wybrany przypadkowo i jest następnie wykorzystywany do generowania
tekstu wyjściowego.
"""
self.parse(self.randomChildElement(node))
def usage():
print __doc__
def main(argv):
grammar = "kant.xml"
try:
opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="])
except getopt.GetoptError:
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
usage()
sys.exit()
elif opt == '-d':
global _debug
_debug = 1
elif opt in ("-g", "--grammar"):
grammar = arg
source = "".join(args)
k = KantGenerator(grammar, source)
print k.output()
if __name__ == "__main__":
main(sys.argv[1:])