add wiki module to the addons-wiki
bzr revid: mga@tinyerp.com-20081114071724-xs1sfus1ukc0jszb
This commit is contained in:
Executable
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>wiki</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2006 Samuel Abels, http://debain.org
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2, as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
class Cell:
|
||||
data = ''
|
||||
colspan = 1
|
||||
rowspan = 1
|
||||
Executable
+290
@@ -0,0 +1,290 @@
|
||||
# Copyright (C) 2006 Samuel Abels, http://debain.org
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2, as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
import HTMLParser, re, sys
|
||||
from Cell import Cell
|
||||
|
||||
class Html2Wiki(HTMLParser.HTMLParser):
|
||||
def __init__(self):
|
||||
HTMLParser.HTMLParser.__init__(self)
|
||||
self.wiki = ''
|
||||
self.buffer = ''
|
||||
self.indent = 0
|
||||
self.linebreak = '\n'
|
||||
self.rows = []
|
||||
self.cells = []
|
||||
self.in_table = False
|
||||
self.in_td = False
|
||||
self.in_heading = False
|
||||
self.in_ul = False
|
||||
self.in_ol = False
|
||||
self.in_li = False
|
||||
self.in_a = False
|
||||
self.in_pre = False
|
||||
self.last_href = ''
|
||||
|
||||
def __output(self, text, linebreak = True):
|
||||
self.buffer += (' ' * self.indent * 2)
|
||||
self.buffer += text + self.linebreak
|
||||
|
||||
def __flush(self):
|
||||
self.wiki += self.buffer
|
||||
self.buffer = ''
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'table': self.start_table()
|
||||
elif tag == 'tr': self.start_tr()
|
||||
elif tag == 'th': self.start_th(attrs)
|
||||
elif tag == 'td': self.start_td(attrs)
|
||||
elif tag == 'h1': self.start_h1()
|
||||
elif tag == 'h2': self.start_h2()
|
||||
elif tag == 'h3': self.start_h3()
|
||||
elif tag == 'ul': self.start_ul()
|
||||
elif tag == 'ol': self.start_ol()
|
||||
elif tag == 'li': self.start_li()
|
||||
elif tag == 'i': self.start_i()
|
||||
elif tag == 'b': self.start_b()
|
||||
elif tag == 'u': self.start_u()
|
||||
elif tag == 'a': self.start_a(attrs)
|
||||
elif tag == 'pre': self.start_pre()
|
||||
elif tag == 'strike': self.start_strike()
|
||||
elif tag == 'br': self.newline()
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == 'table': self.end_table();
|
||||
elif tag == 'tr': self.end_tr()
|
||||
elif tag == 'th': self.end_th()
|
||||
elif tag == 'td': self.end_td()
|
||||
elif tag == 'h1': self.end_h1()
|
||||
elif tag == 'h2': self.end_h2()
|
||||
elif tag == 'h3': self.end_h3()
|
||||
elif tag == 'ul': self.end_ul()
|
||||
elif tag == 'ol': self.end_ol()
|
||||
elif tag == 'li': self.end_li()
|
||||
elif tag == 'i': self.end_i()
|
||||
elif tag == 'b': self.end_b()
|
||||
elif tag == 'u': self.end_u()
|
||||
elif tag == 'a': self.end_a()
|
||||
elif tag == 'pre': self.end_pre()
|
||||
elif tag == 'strike': self.end_strike()
|
||||
|
||||
def start_h1(self):
|
||||
self.buffer += '='
|
||||
|
||||
def end_h1(self):
|
||||
self.buffer += '=\n\n'
|
||||
|
||||
def start_h2(self):
|
||||
self.buffer += '=='
|
||||
|
||||
def end_h2(self):
|
||||
self.buffer += '==\n\n'
|
||||
|
||||
def start_h3(self):
|
||||
self.buffer += '==='
|
||||
|
||||
def end_h3(self):
|
||||
self.buffer += '===\n\n'
|
||||
|
||||
def start_ul(self):
|
||||
self.in_ul = True
|
||||
|
||||
def end_ul(self):
|
||||
self.in_ul = False
|
||||
|
||||
def start_ol(self):
|
||||
self.in_ol = True
|
||||
|
||||
def end_ol(self):
|
||||
self.in_ol = False
|
||||
|
||||
def start_li(self):
|
||||
self.in_li = True
|
||||
if self.in_ol:
|
||||
self.buffer += '# '
|
||||
elif self.in_ul:
|
||||
self.buffer += '* '
|
||||
|
||||
def end_li(self):
|
||||
self.in_li = False
|
||||
|
||||
def start_i(self):
|
||||
self.buffer += '/'
|
||||
|
||||
def end_i(self):
|
||||
self.buffer += '/'
|
||||
|
||||
def start_b(self):
|
||||
self.buffer += '*'
|
||||
|
||||
def end_b(self):
|
||||
self.buffer += '*'
|
||||
|
||||
def start_u(self):
|
||||
self.buffer += '_'
|
||||
|
||||
def end_u(self):
|
||||
self.buffer += '_'
|
||||
|
||||
def start_a(self, attrs):
|
||||
self.in_a = True
|
||||
self.last_href = ''
|
||||
for key, value in attrs:
|
||||
if key == 'href':
|
||||
self.last_href = value
|
||||
self.buffer += '[' + self.last_href
|
||||
|
||||
def end_a(self):
|
||||
self.in_a = False
|
||||
self.buffer += ']'
|
||||
|
||||
def start_pre(self):
|
||||
self.in_pre = True
|
||||
self.buffer += '#Text\n'
|
||||
|
||||
def end_pre(self):
|
||||
self.in_pre = False
|
||||
self.buffer += '#End\n'
|
||||
|
||||
def start_strike(self):
|
||||
self.buffer += '-'
|
||||
|
||||
def end_strike(self):
|
||||
self.buffer += '-'
|
||||
|
||||
def start_table(self):
|
||||
self.in_table = True
|
||||
|
||||
def start_tr(self):
|
||||
pass
|
||||
|
||||
def start_th(self, attrs):
|
||||
self.in_heading = True
|
||||
self.start_td(attrs)
|
||||
|
||||
def start_td(self, attrs):
|
||||
self.__flush()
|
||||
self.in_td = True
|
||||
cell = Cell()
|
||||
for key, value in attrs:
|
||||
if key == 'rowspan':
|
||||
cell.rowspan = int(value)
|
||||
elif key == 'colspan':
|
||||
cell.colspan = int(value)
|
||||
self.cells.append(cell)
|
||||
|
||||
def handle_data(self, data):
|
||||
if not self.in_pre:
|
||||
data = data.replace('\n', '')
|
||||
if self.in_a:
|
||||
if data == self.last_href:
|
||||
return
|
||||
self.buffer += ' '
|
||||
if self.in_li:
|
||||
self.buffer += data.strip() + '\n'
|
||||
if self.in_ul or self.in_ol:
|
||||
self.__flush()
|
||||
elif self.in_td:
|
||||
self.buffer += data
|
||||
elif not self.in_table:
|
||||
self.buffer += data
|
||||
self.__flush()
|
||||
|
||||
def end_td(self):
|
||||
self.cells[-1].data += self.buffer.strip()
|
||||
self.buffer = ''
|
||||
self.in_td = False
|
||||
|
||||
def end_th(self):
|
||||
self.end_td()
|
||||
|
||||
def end_tr(self):
|
||||
if len(self.cells) is 0:
|
||||
return
|
||||
if self.in_heading:
|
||||
self.__output('#Heading')
|
||||
self.in_heading = False
|
||||
else:
|
||||
self.__output('#Row')
|
||||
self.indent += 1
|
||||
line = ('|' * self.cells[0].colspan) + ' ' + self.cells[0].data.strip()
|
||||
for cell in self.cells[1:]:
|
||||
line += ' ' + ('|' * cell.colspan) + ' ' + cell.data.strip()
|
||||
if len(line) <= 80:
|
||||
self.__output(line)
|
||||
else:
|
||||
for cell in self.cells:
|
||||
self.__output(('|' * cell.colspan) + ' ' + cell.data.strip())
|
||||
self.cells = []
|
||||
self.indent -= 1
|
||||
self.__flush()
|
||||
|
||||
def end_table(self):
|
||||
self.in_table = False
|
||||
self.__flush()
|
||||
|
||||
def newline(self):
|
||||
self.buffer += '\n'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import unittest
|
||||
import os
|
||||
from Wiki2Html import Wiki2Html
|
||||
|
||||
class Wiki2HtmlTest(unittest.TestCase):
|
||||
def runTest(self):
|
||||
# Read the entire file into one string.
|
||||
filename = 'markup.txt'
|
||||
infile = open(filename, 'r')
|
||||
in_str = infile.read()
|
||||
infile.close()
|
||||
|
||||
# Convert Wiki to HTML.
|
||||
parser = Wiki2Html()
|
||||
parser.read(filename)
|
||||
html1 = parser.html
|
||||
#print html1
|
||||
|
||||
# Convert the HTML back to Wiki.
|
||||
parser = Html2Wiki()
|
||||
parser.feed(html1)
|
||||
wiki = parser.wiki
|
||||
#print wiki
|
||||
|
||||
# Write the wiki to a file.
|
||||
# The result contains an extra newline to mark a table end.
|
||||
# This is generally ok, but would break the unit test, so we
|
||||
# remove the last character before writing.
|
||||
fd = open(filename + '.tmp', 'w')
|
||||
fd.write(wiki[:-1])
|
||||
fd.close()
|
||||
|
||||
# Convert the new Wiki file to HTML again.
|
||||
parser = Wiki2Html()
|
||||
parser.read(filename + '.tmp')
|
||||
html2 = parser.html
|
||||
#print html2
|
||||
|
||||
# Clean up.
|
||||
os.remove(filename + '.tmp')
|
||||
|
||||
# Make sure that the model is complete.
|
||||
assert len(in_str) > 10
|
||||
assert len(html1) > 10
|
||||
assert html1 == html2
|
||||
|
||||
testcase = Wiki2HtmlTest()
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(testcase)
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import xmlrpclib
|
||||
|
||||
tiny_host = 'localhost'
|
||||
tiny_port = 8000
|
||||
|
||||
url = 'http://%s:%s' % (tiny_host, tiny_port)
|
||||
proxy = xmlrpclib.ServerProxy(url);
|
||||
|
||||
proxy.init('terp', 3, 'admin')
|
||||
print proxy.search('china')
|
||||
Executable
+178
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from SimpleXMLRPCServer import SimpleXMLRPCServer
|
||||
import os
|
||||
import csv
|
||||
from config import *
|
||||
import xmlrpclib
|
||||
import PyLucene
|
||||
import threading
|
||||
|
||||
class IndexServer(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
super(IndexServer,self).__init__()
|
||||
self.path = os.path.join('/tmp', 'index')
|
||||
|
||||
def initIndex(self, database, user, password, new=False):
|
||||
self.database = database
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.new = new
|
||||
self.writer = None
|
||||
|
||||
def open(self):
|
||||
if not os.path.exists(self.path):
|
||||
os.mkdir(path)
|
||||
self.new = True
|
||||
|
||||
store = PyLucene.FSDirectory.getDirectory(path, self.new)
|
||||
self.writer = PyLucene.IndexWriter(store, PyLucene.StandardAnalyzer(), self.new)
|
||||
url = 'http://%s:%s/xmlrpc/object' % (tiny_host, str(tiny_port))
|
||||
self.proxy = xmlrpclib.ServerProxy(url);
|
||||
return True
|
||||
|
||||
def addDocument(self, model, id, data):
|
||||
|
||||
doc = PyLucene.Document()
|
||||
doc.add(PyLucene.Field("model", model,
|
||||
PyLucene.Field.Store.YES,
|
||||
PyLucene.Field.Index.UN_TOKENIZED))
|
||||
indexid=str(id)
|
||||
|
||||
doc.add(PyLucene.Field("id", indexid,
|
||||
PyLucene.Field.Store.YES,
|
||||
PyLucene.Field.Index.UN_TOKENIZED))
|
||||
|
||||
doc.add(PyLucene.Field("contents", data,
|
||||
PyLucene.Field.Store.YES,
|
||||
PyLucene.Field.Index.TOKENIZED))
|
||||
self.writer.addDocument(doc)
|
||||
|
||||
return True
|
||||
|
||||
def doIndex(self, models=None):
|
||||
|
||||
self.open()
|
||||
|
||||
toIndex = []
|
||||
if models is None:
|
||||
toIndex = self.proxy.execute(self.database, self.user, self.password, 'ir.model' ,'search', [])
|
||||
else:
|
||||
toIndex = models
|
||||
|
||||
models = self.proxy.execute(self.database, self.user, self.password, 'ir.model' ,'read', toIndex, ['model'])
|
||||
for mod in models:
|
||||
res_ids = self.proxy.execute(self.database, self.user, self.password, mod['model'], 'search', [])
|
||||
|
||||
if str(mod['model']).startswith('ir'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('res.currency'):
|
||||
continue
|
||||
|
||||
print 'creating index for : ', str(mod['model'])
|
||||
try:
|
||||
res_datas = self.proxy.execute(self.database, self.user, self.password, mod['model'], 'read', res_ids)
|
||||
except Exception, e:
|
||||
print e
|
||||
continue
|
||||
|
||||
for res_data in res_datas:
|
||||
self.addDocument(mod['model'], res_data['id'], ' '.join(map(str,res_data.values())))
|
||||
|
||||
self.new = False
|
||||
self.close()
|
||||
return True
|
||||
|
||||
def reIndex(self, models = None):
|
||||
self.new = True
|
||||
self.doIndex(models)
|
||||
|
||||
def close(self):
|
||||
self.writer.close()
|
||||
return True
|
||||
|
||||
def addRecord(self, model, id, data):
|
||||
self.new = False
|
||||
self.open()
|
||||
doc = PyLucene.Document()
|
||||
doc.add(PyLucene.Field("model", model,
|
||||
PyLucene.Field.Store.YES,
|
||||
PyLucene.Field.Index.UN_TOKENIZED))
|
||||
indexid=str(id)
|
||||
|
||||
doc.add(PyLucene.Field("id", indexid,
|
||||
PyLucene.Field.Store.YES,
|
||||
PyLucene.Field.Index.UN_TOKENIZED))
|
||||
|
||||
doc.add(PyLucene.Field("contents", data,
|
||||
PyLucene.Field.Store.YES,
|
||||
PyLucene.Field.Index.TOKENIZED))
|
||||
self.writer.addDocument(doc)
|
||||
self.close()
|
||||
return True
|
||||
|
||||
def search(self, keys):
|
||||
directory = PyLucene.FSDirectory.getDirectory(self.path, False)
|
||||
searcher = PyLucene.IndexSearcher(directory)
|
||||
analyzer = PyLucene.StandardAnalyzer()
|
||||
|
||||
query = PyLucene.QueryParser("contents", analyzer).parse(keys)
|
||||
hits = searcher.search(query)
|
||||
count= hits.length()
|
||||
result = {}
|
||||
|
||||
for i, doc in hits:
|
||||
model = doc.get('model')
|
||||
id = doc.get('id')
|
||||
|
||||
if result.has_key(model):
|
||||
ids = result.get(model)
|
||||
ids.append(id)
|
||||
else:
|
||||
ids = []
|
||||
ids.append(id)
|
||||
result[model] = ids
|
||||
|
||||
searcher.close()
|
||||
|
||||
if result.__len__() <=0 :
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
||||
def run(self):
|
||||
server = SimpleXMLRPCServer((index_server,index_port),allow_none=True)
|
||||
indexer = IndexServer()
|
||||
server.register_instance(indexer, True)
|
||||
server.register_function(indexer.initIndex, 'init')
|
||||
server.register_function(indexer.open, 'open')
|
||||
server.register_function(indexer.addRecord, 'add')
|
||||
server.register_function(indexer.doIndex, 'index')
|
||||
server.register_function(indexer.reIndex, 'reindex')
|
||||
server.register_function(indexer.close, 'close')
|
||||
server.register_function(indexer.search, 'search')
|
||||
server.register_introspection_functions()
|
||||
server.serve_forever()
|
||||
#end class
|
||||
|
||||
server = SimpleXMLRPCServer((index_server,index_port),allow_none=True)
|
||||
indexer = IndexServer()
|
||||
server.register_function(indexer.initIndex, 'init')
|
||||
server.register_function(indexer.open, 'open')
|
||||
server.register_function(indexer.addRecord, 'add')
|
||||
server.register_function(indexer.doIndex, 'index')
|
||||
server.register_function(indexer.reIndex, 'reindex')
|
||||
server.register_function(indexer.close, 'close')
|
||||
server.register_function(indexer.search, 'search')
|
||||
server.register_introspection_functions()
|
||||
server.register_multicall_functions()
|
||||
server.serve_forever()
|
||||
#
|
||||
#try:
|
||||
# print 'Starting Index Server', index_server, index_port
|
||||
# server = IndexServer()
|
||||
# server.start()
|
||||
#except Exception , e:
|
||||
# print e
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
# Actions for use in token specifications
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
class Action:
|
||||
|
||||
def same_as(self, other):
|
||||
return self is other
|
||||
|
||||
|
||||
class Return(Action):
|
||||
"""
|
||||
Internal Plex action which causes |value| to
|
||||
be returned as the value of the associated token
|
||||
"""
|
||||
|
||||
value = None
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def perform(self, token_stream, text):
|
||||
return self.value
|
||||
|
||||
def same_as(self, other):
|
||||
return isinstance(other, Return) and self.value == other.value
|
||||
|
||||
def __repr__(self):
|
||||
return "Return(%s)" % repr(self.value)
|
||||
|
||||
|
||||
class Call(Action):
|
||||
"""
|
||||
Internal Plex action which causes a function to be called.
|
||||
"""
|
||||
|
||||
function = None
|
||||
|
||||
def __init__(self, function):
|
||||
self.function = function
|
||||
|
||||
def perform(self, token_stream, text):
|
||||
return self.function(token_stream, text)
|
||||
|
||||
def __repr__(self):
|
||||
return "Call(%s)" % self.function.__name__
|
||||
|
||||
def same_as(self, other):
|
||||
return isinstance(other, Call) and self.function is other.function
|
||||
|
||||
|
||||
class Begin(Action):
|
||||
"""
|
||||
Begin(state_name) is a Plex action which causes the Scanner to
|
||||
enter the state |state_name|. See the docstring of Plex.Lexicon
|
||||
for more information.
|
||||
"""
|
||||
|
||||
state_name = None
|
||||
|
||||
def __init__(self, state_name):
|
||||
self.state_name = state_name
|
||||
|
||||
def perform(self, token_stream, text):
|
||||
token_stream.begin(self.state_name)
|
||||
|
||||
def __repr__(self):
|
||||
return "Begin(%s)" % self.state_name
|
||||
|
||||
def same_as(self, other):
|
||||
return isinstance(other, Begin) and self.state_name == other.state_name
|
||||
|
||||
|
||||
class Ignore(Action):
|
||||
"""
|
||||
IGNORE is a Plex action which causes its associated token
|
||||
to be ignored. See the docstring of Plex.Lexicon for more
|
||||
information.
|
||||
"""
|
||||
def perform(self, token_stream, text):
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return "IGNORE"
|
||||
|
||||
IGNORE = Ignore()
|
||||
IGNORE.__doc__ = Ignore.__doc__
|
||||
|
||||
class Text(Action):
|
||||
"""
|
||||
TEXT is a Plex action which causes the text of a token to
|
||||
be returned as the value of the token. See the docstring of
|
||||
Plex.Lexicon for more information.
|
||||
"""
|
||||
|
||||
def perform(self, token_stream, text):
|
||||
return text
|
||||
|
||||
def __repr__(self):
|
||||
return "TEXT"
|
||||
|
||||
TEXT = Text()
|
||||
TEXT.__doc__ = Text.__doc__
|
||||
|
||||
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
# Converting NFA to DFA
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
import Machines
|
||||
from Machines import LOWEST_PRIORITY
|
||||
from Transitions import TransitionMap
|
||||
|
||||
def nfa_to_dfa(old_machine, debug = None):
|
||||
"""
|
||||
Given a nondeterministic Machine, return a new equivalent
|
||||
Machine which is deterministic.
|
||||
"""
|
||||
# We build a new machine whose states correspond to sets of states
|
||||
# in the old machine. Initially we add a new state corresponding to
|
||||
# the epsilon-closure of each initial old state. Then we give transitions
|
||||
# to each new state which are the union of all transitions out of any
|
||||
# of the corresponding old states. The new state reached on a given
|
||||
# character is the one corresponding to the set of states reachable
|
||||
# on that character from any of the old states. As new combinations of
|
||||
# old states are created, new states are added as needed until closure
|
||||
# is reached.
|
||||
new_machine = Machines.FastMachine()
|
||||
state_map = StateMap(new_machine)
|
||||
# Seed the process using the initial states of the old machine.
|
||||
# Make the corresponding new states into initial states of the new
|
||||
# machine with the same names.
|
||||
for (key, old_state) in old_machine.initial_states.items():
|
||||
new_state = state_map.old_to_new(epsilon_closure(old_state))
|
||||
new_machine.make_initial_state(key, new_state)
|
||||
# Tricky bit here: we add things to the end of this list while we're
|
||||
# iterating over it. The iteration stops when closure is achieved.
|
||||
for new_state in new_machine.states:
|
||||
transitions = TransitionMap()
|
||||
for old_state in state_map.new_to_old(new_state).keys():
|
||||
for event, old_target_states in old_state.transitions.items():
|
||||
if event and old_target_states:
|
||||
transitions.add_set(event, set_epsilon_closure(old_target_states))
|
||||
for event, old_states in transitions.items():
|
||||
new_machine.add_transitions(new_state, event, state_map.old_to_new(old_states))
|
||||
if debug:
|
||||
debug.write("\n===== State Mapping =====\n")
|
||||
state_map.dump(debug)
|
||||
return new_machine
|
||||
|
||||
def set_epsilon_closure(state_set):
|
||||
"""
|
||||
Given a set of states, return the union of the epsilon
|
||||
closures of its member states.
|
||||
"""
|
||||
result = {}
|
||||
for state1 in state_set.keys():
|
||||
for state2 in epsilon_closure(state1).keys():
|
||||
result[state2] = 1
|
||||
return result
|
||||
|
||||
def epsilon_closure(state):
|
||||
"""
|
||||
Return the set of states reachable from the given state
|
||||
by epsilon moves.
|
||||
"""
|
||||
# Cache the result
|
||||
result = state.epsilon_closure
|
||||
if result is None:
|
||||
result = {}
|
||||
state.epsilon_closure = result
|
||||
add_to_epsilon_closure(result, state)
|
||||
return result
|
||||
|
||||
def add_to_epsilon_closure(state_set, state):
|
||||
"""
|
||||
Recursively add to |state_set| states reachable from the given state
|
||||
by epsilon moves.
|
||||
"""
|
||||
if not state_set.get(state, 0):
|
||||
state_set[state] = 1
|
||||
state_set_2 = state.transitions.get_epsilon()
|
||||
if state_set_2:
|
||||
for state2 in state_set_2.keys():
|
||||
add_to_epsilon_closure(state_set, state2)
|
||||
|
||||
class StateMap:
|
||||
"""
|
||||
Helper class used by nfa_to_dfa() to map back and forth between
|
||||
sets of states from the old machine and states of the new machine.
|
||||
"""
|
||||
new_machine = None # Machine
|
||||
old_to_new_dict = None # {(old_state,...) : new_state}
|
||||
new_to_old_dict = None # {id(new_state) : old_state_set}
|
||||
|
||||
def __init__(self, new_machine):
|
||||
self.new_machine = new_machine
|
||||
self.old_to_new_dict = {}
|
||||
self.new_to_old_dict= {}
|
||||
|
||||
def old_to_new(self, old_state_set):
|
||||
"""
|
||||
Return the state of the new machine corresponding to the
|
||||
set of old machine states represented by |state_set|. A new
|
||||
state will be created if necessary. If any of the old states
|
||||
are accepting states, the new state will be an accepting state
|
||||
with the highest priority action from the old states.
|
||||
"""
|
||||
key = self.make_key(old_state_set)
|
||||
new_state = self.old_to_new_dict.get(key, None)
|
||||
if not new_state:
|
||||
action = self.highest_priority_action(old_state_set)
|
||||
new_state = self.new_machine.new_state(action)
|
||||
self.old_to_new_dict[key] = new_state
|
||||
self.new_to_old_dict[id(new_state)] = old_state_set
|
||||
#for old_state in old_state_set.keys():
|
||||
#new_state.merge_actions(old_state)
|
||||
return new_state
|
||||
|
||||
def highest_priority_action(self, state_set):
|
||||
best_action = None
|
||||
best_priority = LOWEST_PRIORITY
|
||||
for state in state_set.keys():
|
||||
priority = state.action_priority
|
||||
if priority > best_priority:
|
||||
best_action = state.action
|
||||
best_priority = priority
|
||||
return best_action
|
||||
|
||||
# def old_to_new_set(self, old_state_set):
|
||||
# """
|
||||
# Return the new state corresponding to a set of old states as
|
||||
# a singleton set.
|
||||
# """
|
||||
# return {self.old_to_new(old_state_set):1}
|
||||
|
||||
def new_to_old(self, new_state):
|
||||
"""Given a new state, return a set of corresponding old states."""
|
||||
return self.new_to_old_dict[id(new_state)]
|
||||
|
||||
def make_key(self, state_set):
|
||||
"""
|
||||
Convert a set of states into a uniquified
|
||||
sorted tuple suitable for use as a dictionary key.
|
||||
"""
|
||||
lst = state_set.keys()
|
||||
lst.sort()
|
||||
return tuple(lst)
|
||||
|
||||
def dump(self, file):
|
||||
from Transitions import state_set_str
|
||||
for new_state in self.new_machine.states:
|
||||
old_state_set = self.new_to_old_dict[id(new_state)]
|
||||
file.write(" State %s <-- %s\n" % (
|
||||
new_state['number'], state_set_str(old_state_set)))
|
||||
|
||||
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
# Exception classes
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
import exceptions
|
||||
|
||||
class PlexError(exceptions.Exception):
|
||||
message = ""
|
||||
|
||||
class PlexTypeError(PlexError, TypeError):
|
||||
pass
|
||||
|
||||
class PlexValueError(PlexError, ValueError):
|
||||
pass
|
||||
|
||||
class InvalidRegex(PlexError):
|
||||
pass
|
||||
|
||||
class InvalidToken(PlexError):
|
||||
|
||||
def __init__(self, token_number, message):
|
||||
PlexError.__init__(self, "Token number %d: %s" % (token_number, message))
|
||||
|
||||
class InvalidScanner(PlexError):
|
||||
pass
|
||||
|
||||
class AmbiguousAction(PlexError):
|
||||
message = "Two tokens with different actions can match the same string"
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
class UnrecognizedInput(PlexError):
|
||||
scanner = None
|
||||
position = None
|
||||
state_name = None
|
||||
|
||||
def __init__(self, scanner, state_name):
|
||||
self.scanner = scanner
|
||||
self.position = scanner.position()
|
||||
self.state_name = state_name
|
||||
|
||||
def __str__(self):
|
||||
return ("'%s', line %d, char %d: Token not recognised in state %s"
|
||||
% (self.position + (repr(self.state_name),)))
|
||||
|
||||
|
||||
|
||||
Executable
+192
@@ -0,0 +1,192 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
# Lexical Analyser Specification
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
import types
|
||||
|
||||
import Actions
|
||||
import DFA
|
||||
import Errors
|
||||
import Machines
|
||||
import Regexps
|
||||
|
||||
# debug_flags for Lexicon constructor
|
||||
DUMP_NFA = 1
|
||||
DUMP_DFA = 2
|
||||
|
||||
class State:
|
||||
"""
|
||||
This class is used as part of a Plex.Lexicon specification to
|
||||
introduce a user-defined state.
|
||||
|
||||
Constructor:
|
||||
|
||||
State(name, token_specifications)
|
||||
"""
|
||||
|
||||
name = None
|
||||
tokens = None
|
||||
|
||||
def __init__(self, name, tokens):
|
||||
self.name = name
|
||||
self.tokens = tokens
|
||||
|
||||
class Lexicon:
|
||||
"""
|
||||
Lexicon(specification) builds a lexical analyser from the given
|
||||
|specification|. The specification consists of a list of
|
||||
specification items. Each specification item may be either:
|
||||
|
||||
1) A token definition, which is a tuple:
|
||||
|
||||
(pattern, action)
|
||||
|
||||
The |pattern| is a regular axpression built using the
|
||||
constructors defined in the Plex module.
|
||||
|
||||
The |action| is the action to be performed when this pattern
|
||||
is recognised (see below).
|
||||
|
||||
2) A state definition:
|
||||
|
||||
State(name, tokens)
|
||||
|
||||
where |name| is a character string naming the state,
|
||||
and |tokens| is a list of token definitions as
|
||||
above. The meaning and usage of states is described
|
||||
below.
|
||||
|
||||
Actions
|
||||
-------
|
||||
|
||||
The |action| in a token specication may be one of three things:
|
||||
|
||||
1) A function, which is called as follows:
|
||||
|
||||
function(scanner, text)
|
||||
|
||||
where |scanner| is the relevant Scanner instance, and |text|
|
||||
is the matched text. If the function returns anything
|
||||
other than None, that value is returned as the value of the
|
||||
token. If it returns None, scanning continues as if the IGNORE
|
||||
action were specified (see below).
|
||||
|
||||
2) One of the following special actions:
|
||||
|
||||
IGNORE means that the recognised characters will be treated as
|
||||
white space and ignored. Scanning will continue until
|
||||
the next non-ignored token is recognised before returning.
|
||||
|
||||
TEXT causes the scanned text itself to be returned as the
|
||||
value of the token.
|
||||
|
||||
3) Any other value, which is returned as the value of the token.
|
||||
|
||||
States
|
||||
------
|
||||
|
||||
At any given time, the scanner is in one of a number of states.
|
||||
Associated with each state is a set of possible tokens. When scanning,
|
||||
only tokens associated with the current state are recognised.
|
||||
|
||||
There is a default state, whose name is the empty string. Token
|
||||
definitions which are not inside any State definition belong to
|
||||
the default state.
|
||||
|
||||
The initial state of the scanner is the default state. The state can
|
||||
be changed in one of two ways:
|
||||
|
||||
1) Using Begin(state_name) as the action of a token.
|
||||
|
||||
2) Calling the begin(state_name) method of the Scanner.
|
||||
|
||||
To change back to the default state, use '' as the state name.
|
||||
"""
|
||||
|
||||
machine = None # Machine
|
||||
tables = None # StateTableMachine
|
||||
|
||||
def __init__(self, specifications, debug = None, debug_flags = 7, timings = None):
|
||||
if type(specifications) <> types.ListType:
|
||||
raise Errors.InvalidScanner("Scanner definition is not a list")
|
||||
if timings:
|
||||
from Timing import time
|
||||
total_time = 0.0
|
||||
time1 = time()
|
||||
nfa = Machines.Machine()
|
||||
default_initial_state = nfa.new_initial_state('')
|
||||
token_number = 1
|
||||
for spec in specifications:
|
||||
if isinstance(spec, State):
|
||||
user_initial_state = nfa.new_initial_state(spec.name)
|
||||
for token in spec.tokens:
|
||||
self.add_token_to_machine(
|
||||
nfa, user_initial_state, token, token_number)
|
||||
token_number = token_number + 1
|
||||
elif type(spec) == types.TupleType:
|
||||
self.add_token_to_machine(
|
||||
nfa, default_initial_state, spec, token_number)
|
||||
token_number = token_number + 1
|
||||
else:
|
||||
raise Errors.InvalidToken(
|
||||
token_number,
|
||||
"Expected a token definition (tuple) or State instance")
|
||||
if timings:
|
||||
time2 = time()
|
||||
total_time = total_time + (time2 - time1)
|
||||
time3 = time()
|
||||
if debug and (debug_flags & 1):
|
||||
debug.write("\n============= NFA ===========\n")
|
||||
nfa.dump(debug)
|
||||
dfa = DFA.nfa_to_dfa(nfa, debug = (debug_flags & 3) == 3 and debug)
|
||||
if timings:
|
||||
time4 = time()
|
||||
total_time = total_time + (time4 - time3)
|
||||
if debug and (debug_flags & 2):
|
||||
debug.write("\n============= DFA ===========\n")
|
||||
dfa.dump(debug)
|
||||
if timings:
|
||||
timings.write("Constructing NFA : %5.2f\n" % (time2 - time1))
|
||||
timings.write("Converting to DFA: %5.2f\n" % (time4 - time3))
|
||||
timings.write("TOTAL : %5.2f\n" % total_time)
|
||||
self.machine = dfa
|
||||
|
||||
def add_token_to_machine(self, machine, initial_state, token_spec, token_number):
|
||||
try:
|
||||
(re, action_spec) = self.parse_token_definition(token_spec)
|
||||
# Disabled this -- matching empty strings can be useful
|
||||
#if re.nullable:
|
||||
# raise Errors.InvalidToken(
|
||||
# token_number, "Pattern can match 0 input symbols")
|
||||
if isinstance(action_spec, Actions.Action):
|
||||
action = action_spec
|
||||
elif callable(action_spec):
|
||||
action = Actions.Call(action_spec)
|
||||
else:
|
||||
action = Actions.Return(action_spec)
|
||||
final_state = machine.new_state()
|
||||
re.build_machine(machine, initial_state, final_state,
|
||||
match_bol = 1, nocase = 0)
|
||||
final_state.set_action(action, priority = -token_number)
|
||||
except Errors.PlexError, e:
|
||||
raise e.__class__("Token number %d: %s" % (token_number, e))
|
||||
|
||||
def parse_token_definition(self, token_spec):
|
||||
if type(token_spec) <> types.TupleType:
|
||||
raise Errors.InvalidToken("Token definition is not a tuple")
|
||||
if len(token_spec) <> 2:
|
||||
raise Errors.InvalidToken("Wrong number of items in token definition")
|
||||
pattern, action = token_spec
|
||||
if not isinstance(pattern, Regexps.RE):
|
||||
raise Errors.InvalidToken("Pattern is not an RE instance")
|
||||
return (pattern, action)
|
||||
|
||||
def get_initial_state(self, name):
|
||||
return self.machine.get_initial_state(name)
|
||||
|
||||
|
||||
|
||||
Executable
+326
@@ -0,0 +1,326 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
# Classes for building NFAs and DFAs
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
import string
|
||||
import sys
|
||||
from sys import maxint
|
||||
from types import TupleType
|
||||
|
||||
from Transitions import TransitionMap
|
||||
|
||||
LOWEST_PRIORITY = -sys.maxint
|
||||
|
||||
class Machine:
|
||||
"""A collection of Nodes representing an NFA or DFA."""
|
||||
states = None # [Node]
|
||||
next_state_number = 1
|
||||
initial_states = None # {(name, bol): Node}
|
||||
|
||||
def __init__(self):
|
||||
self.states = []
|
||||
self.initial_states = {}
|
||||
|
||||
def __del__(self):
|
||||
#print "Destroying", self ###
|
||||
for state in self.states:
|
||||
state.destroy()
|
||||
|
||||
def new_state(self):
|
||||
"""Add a new state to the machine and return it."""
|
||||
s = Node()
|
||||
n = self.next_state_number
|
||||
self.next_state_number = n + 1
|
||||
s.number = n
|
||||
self.states.append(s)
|
||||
return s
|
||||
|
||||
def new_initial_state(self, name):
|
||||
state = self.new_state()
|
||||
self.make_initial_state(name, state)
|
||||
return state
|
||||
|
||||
def make_initial_state(self, name, state):
|
||||
self.initial_states[name] = state
|
||||
|
||||
def get_initial_state(self, name):
|
||||
return self.initial_states[name]
|
||||
|
||||
def dump(self, file):
|
||||
file.write("Plex.Machine:\n")
|
||||
if self.initial_states is not None:
|
||||
file.write(" Initial states:\n")
|
||||
for (name, state) in self.initial_states.items():
|
||||
file.write(" '%s': %d\n" % (name, state.number))
|
||||
for s in self.states:
|
||||
s.dump(file)
|
||||
|
||||
class Node:
|
||||
"""A state of an NFA or DFA."""
|
||||
transitions = None # TransitionMap
|
||||
action = None # Action
|
||||
action_priority = None # integer
|
||||
number = 0 # for debug output
|
||||
epsilon_closure = None # used by nfa_to_dfa()
|
||||
|
||||
def __init__(self):
|
||||
# Preinitialise the list of empty transitions, because
|
||||
# the nfa-to-dfa algorithm needs it
|
||||
#self.transitions = {'':[]}
|
||||
self.transitions = TransitionMap()
|
||||
self.action_priority = LOWEST_PRIORITY
|
||||
|
||||
def destroy(self):
|
||||
#print "Destroying", self ###
|
||||
self.transitions = None
|
||||
self.action = None
|
||||
self.epsilon_closure = None
|
||||
|
||||
def add_transition(self, event, new_state):
|
||||
self.transitions.add(event, new_state)
|
||||
|
||||
def link_to(self, state):
|
||||
"""Add an epsilon-move from this state to another state."""
|
||||
self.add_transition('', state)
|
||||
|
||||
def set_action(self, action, priority):
|
||||
"""Make this an accepting state with the given action. If
|
||||
there is already an action, choose the action with highest
|
||||
priority."""
|
||||
if priority > self.action_priority:
|
||||
self.action = action
|
||||
self.action_priority = priority
|
||||
|
||||
def get_action(self):
|
||||
return self.action
|
||||
|
||||
def get_action_priority(self):
|
||||
return self.action_priority
|
||||
|
||||
# def merge_actions(self, other_state):
|
||||
# """Merge actions of other state into this state according
|
||||
# to their priorities."""
|
||||
# action = other_state.get_action()
|
||||
# priority = other_state.get_action_priority()
|
||||
# self.set_action(action, priority)
|
||||
|
||||
def is_accepting(self):
|
||||
return self.action is not None
|
||||
|
||||
def __str__(self):
|
||||
return "State %d" % self.number
|
||||
|
||||
def dump(self, file):
|
||||
import string
|
||||
# Header
|
||||
file.write(" State %d:\n" % self.number)
|
||||
# Transitions
|
||||
# self.dump_transitions(file)
|
||||
self.transitions.dump(file)
|
||||
# Action
|
||||
action = self.action
|
||||
priority = self.action_priority
|
||||
if action is not None:
|
||||
file.write(" %s [priority %d]\n" % (action, priority))
|
||||
|
||||
|
||||
class FastMachine:
|
||||
"""
|
||||
FastMachine is a deterministic machine represented in a way that
|
||||
allows fast scanning.
|
||||
"""
|
||||
initial_states = None # {state_name:state}
|
||||
states = None # [state]
|
||||
# where state = {event:state, 'else':state, 'action':Action}
|
||||
next_number = 1 # for debugging
|
||||
|
||||
new_state_template = {
|
||||
'':None, 'bol':None, 'eol':None, 'eof':None, 'else':None
|
||||
}
|
||||
|
||||
def __init__(self, old_machine = None):
|
||||
self.initial_states = initial_states = {}
|
||||
self.states = []
|
||||
if old_machine:
|
||||
self.old_to_new = old_to_new = {}
|
||||
for old_state in old_machine.states:
|
||||
new_state = self.new_state()
|
||||
old_to_new[old_state] = new_state
|
||||
for name, old_state in old_machine.initial_states.items():
|
||||
initial_states[name] = old_to_new[old_state]
|
||||
for old_state in old_machine.states:
|
||||
new_state = old_to_new[old_state]
|
||||
for event, old_state_set in old_state.transitions.items():
|
||||
if old_state_set:
|
||||
new_state[event] = old_to_new[old_state_set.keys()[0]]
|
||||
else:
|
||||
new_state[event] = None
|
||||
new_state['action'] = old_state.action
|
||||
|
||||
def __del__(self):
|
||||
for state in self.states:
|
||||
state.clear()
|
||||
|
||||
def new_state(self, action = None):
|
||||
number = self.next_number
|
||||
self.next_number = number + 1
|
||||
result = self.new_state_template.copy()
|
||||
result['number'] = number
|
||||
result['action'] = action
|
||||
self.states.append(result)
|
||||
return result
|
||||
|
||||
def make_initial_state(self, name, state):
|
||||
self.initial_states[name] = state
|
||||
|
||||
def add_transitions(self, state, event, new_state):
|
||||
if type(event) == TupleType:
|
||||
code0, code1 = event
|
||||
if code0 == -maxint:
|
||||
state['else'] = new_state
|
||||
elif code1 <> maxint:
|
||||
while code0 < code1:
|
||||
state[chr(code0)] = new_state
|
||||
code0 = code0 + 1
|
||||
else:
|
||||
state[event] = new_state
|
||||
|
||||
def get_initial_state(self, name):
|
||||
return self.initial_states[name]
|
||||
|
||||
def dump(self, file):
|
||||
file.write("Plex.FastMachine:\n")
|
||||
file.write(" Initial states:\n")
|
||||
for name, state in self.initial_states.items():
|
||||
file.write(" %s: %s\n" % (repr(name), state['number']))
|
||||
for state in self.states:
|
||||
self.dump_state(state, file)
|
||||
|
||||
def dump_state(self, state, file):
|
||||
import string
|
||||
# Header
|
||||
file.write(" State %d:\n" % state['number'])
|
||||
# Transitions
|
||||
self.dump_transitions(state, file)
|
||||
# Action
|
||||
action = state['action']
|
||||
if action is not None:
|
||||
file.write(" %s\n" % action)
|
||||
|
||||
def dump_transitions(self, state, file):
|
||||
chars_leading_to_state = {}
|
||||
special_to_state = {}
|
||||
for (c, s) in state.items():
|
||||
if len(c) == 1:
|
||||
chars = chars_leading_to_state.get(id(s), None)
|
||||
if chars is None:
|
||||
chars = []
|
||||
chars_leading_to_state[id(s)] = chars
|
||||
chars.append(c)
|
||||
elif len(c) <= 4:
|
||||
special_to_state[c] = s
|
||||
ranges_to_state = {}
|
||||
for state in self.states:
|
||||
char_list = chars_leading_to_state.get(id(state), None)
|
||||
if char_list:
|
||||
ranges = self.chars_to_ranges(char_list)
|
||||
ranges_to_state[ranges] = state
|
||||
ranges_list = ranges_to_state.keys()
|
||||
ranges_list.sort()
|
||||
for ranges in ranges_list:
|
||||
key = self.ranges_to_string(ranges)
|
||||
state = ranges_to_state[ranges]
|
||||
file.write(" %s --> State %d\n" % (key, state['number']))
|
||||
for key in ('bol', 'eol', 'eof', 'else'):
|
||||
state = special_to_state.get(key, None)
|
||||
if state:
|
||||
file.write(" %s --> State %d\n" % (key, state['number']))
|
||||
|
||||
def chars_to_ranges(self, char_list):
|
||||
char_list.sort()
|
||||
i = 0
|
||||
n = len(char_list)
|
||||
result = []
|
||||
while i < n:
|
||||
c1 = ord(char_list[i])
|
||||
c2 = c1
|
||||
i = i + 1
|
||||
while i < n and ord(char_list[i]) == c2 + 1:
|
||||
i = i + 1
|
||||
c2 = c2 + 1
|
||||
result.append((chr(c1), chr(c2)))
|
||||
return tuple(result)
|
||||
|
||||
def ranges_to_string(self, range_list):
|
||||
return string.join(map(self.range_to_string, range_list), ",")
|
||||
|
||||
def range_to_string(self, (c1, c2)):
|
||||
if c1 == c2:
|
||||
return repr(c1)
|
||||
else:
|
||||
return "%s..%s" % (repr(c1), repr(c2))
|
||||
##
|
||||
## (Superseded by Machines.FastMachine)
|
||||
##
|
||||
## class StateTableMachine:
|
||||
## """
|
||||
## StateTableMachine is an alternative representation of a Machine
|
||||
## that can be run more efficiently.
|
||||
## """
|
||||
## initial_states = None # {state_name:state_index}
|
||||
## states = None # [([state] indexed by char code, Action)]
|
||||
|
||||
## special_map = {'bol':256, 'eol':257, 'eof':258}
|
||||
|
||||
## def __init__(self, m):
|
||||
## """
|
||||
## Initialise StateTableMachine from Machine |m|.
|
||||
## """
|
||||
## initial_states = self.initial_states = {}
|
||||
## states = self.states = [None]
|
||||
## old_to_new = {}
|
||||
## i = 1
|
||||
## for old_state in m.states:
|
||||
## new_state = ([0] * 259, old_state.get_action())
|
||||
## states.append(new_state)
|
||||
## old_to_new[old_state] = i # new_state
|
||||
## i = i + 1
|
||||
## for name, old_state in m.initial_states.items():
|
||||
## initial_states[name] = old_to_new[old_state]
|
||||
## for old_state in m.states:
|
||||
## new_state_index = old_to_new[old_state]
|
||||
## new_table = states[new_state_index][0]
|
||||
## transitions = old_state.transitions
|
||||
## for c, old_targets in transitions.items():
|
||||
## if old_targets:
|
||||
## old_target = old_targets[0]
|
||||
## new_target_index = old_to_new[old_target]
|
||||
## if len(c) == 1:
|
||||
## a = ord(c)
|
||||
## else:
|
||||
## a = self.special_map[c]
|
||||
## new_table[a] = states[new_target_index]
|
||||
|
||||
## def dump(self, f):
|
||||
## f.write("Plex.StateTableMachine:\n")
|
||||
## f.write(" Initial states:\n")
|
||||
## for name, index in self.initial_states.items():
|
||||
## f.write(" %s: State %d\n" % (
|
||||
## repr(name), id(self.states[index])))
|
||||
## for i in xrange(1, len(self.states)):
|
||||
## table, action = self.states[i]
|
||||
## f.write(" State %d:" % i)
|
||||
## if action:
|
||||
## f.write("%s" % action)
|
||||
## f.write("\n")
|
||||
## f.write(" %s\n" % map(id,table))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Executable
+557
@@ -0,0 +1,557 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
# Regular Expressions
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
import array
|
||||
import string
|
||||
import types
|
||||
from sys import maxint
|
||||
|
||||
import Errors
|
||||
|
||||
#
|
||||
# Constants
|
||||
#
|
||||
|
||||
BOL = 'bol'
|
||||
EOL = 'eol'
|
||||
EOF = 'eof'
|
||||
|
||||
nl_code = ord('\n')
|
||||
|
||||
#
|
||||
# Helper functions
|
||||
#
|
||||
|
||||
def chars_to_ranges(s):
|
||||
"""
|
||||
Return a list of character codes consisting of pairs
|
||||
[code1a, code1b, code2a, code2b,...] which cover all
|
||||
the characters in |s|.
|
||||
"""
|
||||
char_list = list(s)
|
||||
char_list.sort()
|
||||
i = 0
|
||||
n = len(char_list)
|
||||
result = []
|
||||
while i < n:
|
||||
code1 = ord(char_list[i])
|
||||
code2 = code1 + 1
|
||||
i = i + 1
|
||||
while i < n and code2 >= ord(char_list[i]):
|
||||
code2 = code2 + 1
|
||||
i = i + 1
|
||||
result.append(code1)
|
||||
result.append(code2)
|
||||
return result
|
||||
|
||||
def uppercase_range(code1, code2):
|
||||
"""
|
||||
If the range of characters from code1 to code2-1 includes any
|
||||
lower case letters, return the corresponding upper case range.
|
||||
"""
|
||||
code3 = max(code1, ord('a'))
|
||||
code4 = min(code2, ord('z') + 1)
|
||||
if code3 < code4:
|
||||
d = ord('A') - ord('a')
|
||||
return (code3 + d, code4 + d)
|
||||
else:
|
||||
return None
|
||||
|
||||
def lowercase_range(code1, code2):
|
||||
"""
|
||||
If the range of characters from code1 to code2-1 includes any
|
||||
upper case letters, return the corresponding lower case range.
|
||||
"""
|
||||
code3 = max(code1, ord('A'))
|
||||
code4 = min(code2, ord('Z') + 1)
|
||||
if code3 < code4:
|
||||
d = ord('a') - ord('A')
|
||||
return (code3 + d, code4 + d)
|
||||
else:
|
||||
return None
|
||||
|
||||
def CodeRanges(code_list):
|
||||
"""
|
||||
Given a list of codes as returned by chars_to_ranges, return
|
||||
an RE which will match a character in any of the ranges.
|
||||
"""
|
||||
re_list = []
|
||||
for i in xrange(0, len(code_list), 2):
|
||||
re_list.append(CodeRange(code_list[i], code_list[i + 1]))
|
||||
return apply(Alt, tuple(re_list))
|
||||
|
||||
def CodeRange(code1, code2):
|
||||
"""
|
||||
CodeRange(code1, code2) is an RE which matches any character
|
||||
with a code |c| in the range |code1| <= |c| < |code2|.
|
||||
"""
|
||||
if code1 <= nl_code < code2:
|
||||
return Alt(RawCodeRange(code1, nl_code),
|
||||
RawNewline,
|
||||
RawCodeRange(nl_code + 1, code2))
|
||||
else:
|
||||
return RawCodeRange(code1, code2)
|
||||
|
||||
#
|
||||
# Abstract classes
|
||||
#
|
||||
|
||||
class RE:
|
||||
"""RE is the base class for regular expression constructors.
|
||||
The following operators are defined on REs:
|
||||
|
||||
re1 + re2 is an RE which matches |re1| followed by |re2|
|
||||
re1 | re2 is an RE which matches either |re1| or |re2|
|
||||
"""
|
||||
|
||||
nullable = 1 # True if this RE can match 0 input symbols
|
||||
match_nl = 1 # True if this RE can match a string ending with '\n'
|
||||
str = None # Set to a string to override the class's __str__ result
|
||||
|
||||
def build_machine(self, machine, initial_state, final_state,
|
||||
match_bol, nocase):
|
||||
"""
|
||||
This method should add states to |machine| to implement this
|
||||
RE, starting at |initial_state| and ending at |final_state|.
|
||||
If |match_bol| is true, the RE must be able to match at the
|
||||
beginning of a line. If nocase is true, upper and lower case
|
||||
letters should be treated as equivalent.
|
||||
"""
|
||||
raise exceptions.UnimplementedMethod("%s.build_machine not implemented" %
|
||||
self.__class__.__name__)
|
||||
|
||||
def build_opt(self, m, initial_state, c):
|
||||
"""
|
||||
Given a state |s| of machine |m|, return a new state
|
||||
reachable from |s| on character |c| or epsilon.
|
||||
"""
|
||||
s = m.new_state()
|
||||
initial_state.link_to(s)
|
||||
initial_state.add_transition(c, s)
|
||||
return s
|
||||
|
||||
def __add__(self, other):
|
||||
return Seq(self, other)
|
||||
|
||||
def __or__(self, other):
|
||||
return Alt(self, other)
|
||||
|
||||
def __str__(self):
|
||||
if self.str:
|
||||
return self.str
|
||||
else:
|
||||
return self.calc_str()
|
||||
|
||||
def check_re(self, num, value):
|
||||
if not isinstance(value, RE):
|
||||
self.wrong_type(num, value, "Plex.RE instance")
|
||||
|
||||
def check_string(self, num, value):
|
||||
if type(value) <> type(''):
|
||||
self.wrong_type(num, value, "string")
|
||||
|
||||
def check_char(self, num, value):
|
||||
self.check_string(num, value)
|
||||
if len(value) <> 1:
|
||||
raise Errors.PlexValueError("Invalid value for argument %d of Plex.%s."
|
||||
"Expected a string of length 1, got: %s" % (
|
||||
num, self.__class__.__name__, repr(value)))
|
||||
|
||||
def wrong_type(self, num, value, expected):
|
||||
if type(value) == types.InstanceType:
|
||||
got = "%s.%s instance" % (
|
||||
value.__class__.__module__, value.__class__.__name__)
|
||||
else:
|
||||
got = type(value).__name__
|
||||
raise Errors.PlexTypeError("Invalid type for argument %d of Plex.%s "
|
||||
"(expected %s, got %s" % (
|
||||
num, self.__class__.__name__, expected, got))
|
||||
|
||||
#
|
||||
# Primitive RE constructors
|
||||
# -------------------------
|
||||
#
|
||||
# These are the basic REs from which all others are built.
|
||||
#
|
||||
|
||||
## class Char(RE):
|
||||
## """
|
||||
## Char(c) is an RE which matches the character |c|.
|
||||
## """
|
||||
|
||||
## nullable = 0
|
||||
|
||||
## def __init__(self, char):
|
||||
## self.char = char
|
||||
## self.match_nl = char == '\n'
|
||||
|
||||
## def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
## c = self.char
|
||||
## if match_bol and c <> BOL:
|
||||
## s1 = self.build_opt(m, initial_state, BOL)
|
||||
## else:
|
||||
## s1 = initial_state
|
||||
## if c == '\n' or c == EOF:
|
||||
## s1 = self.build_opt(m, s1, EOL)
|
||||
## if len(c) == 1:
|
||||
## code = ord(self.char)
|
||||
## s1.add_transition((code, code+1), final_state)
|
||||
## if nocase and is_letter_code(code):
|
||||
## code2 = other_case_code(code)
|
||||
## s1.add_transition((code2, code2+1), final_state)
|
||||
## else:
|
||||
## s1.add_transition(c, final_state)
|
||||
|
||||
## def calc_str(self):
|
||||
## return "Char(%s)" % repr(self.char)
|
||||
|
||||
def Char(c):
|
||||
"""
|
||||
Char(c) is an RE which matches the character |c|.
|
||||
"""
|
||||
if len(c) == 1:
|
||||
result = CodeRange(ord(c), ord(c) + 1)
|
||||
else:
|
||||
result = SpecialSymbol(c)
|
||||
result.str = "Char(%s)" % repr(c)
|
||||
return result
|
||||
|
||||
class RawCodeRange(RE):
|
||||
"""
|
||||
RawCodeRange(code1, code2) is a low-level RE which matches any character
|
||||
with a code |c| in the range |code1| <= |c| < |code2|, where the range
|
||||
does not include newline. For internal use only.
|
||||
"""
|
||||
nullable = 0
|
||||
match_nl = 0
|
||||
range = None # (code, code)
|
||||
uppercase_range = None # (code, code) or None
|
||||
lowercase_range = None # (code, code) or None
|
||||
|
||||
def __init__(self, code1, code2):
|
||||
self.range = (code1, code2)
|
||||
self.uppercase_range = uppercase_range(code1, code2)
|
||||
self.lowercase_range = lowercase_range(code1, code2)
|
||||
|
||||
def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
if match_bol:
|
||||
initial_state = self.build_opt(m, initial_state, BOL)
|
||||
initial_state.add_transition(self.range, final_state)
|
||||
if nocase:
|
||||
if self.uppercase_range:
|
||||
initial_state.add_transition(self.uppercase_range, final_state)
|
||||
if self.lowercase_range:
|
||||
initial_state.add_transition(self.lowercase_range, final_state)
|
||||
|
||||
def calc_str(self):
|
||||
return "CodeRange(%d,%d)" % (self.code1, self.code2)
|
||||
|
||||
class _RawNewline(RE):
|
||||
"""
|
||||
RawNewline is a low-level RE which matches a newline character.
|
||||
For internal use only.
|
||||
"""
|
||||
nullable = 0
|
||||
match_nl = 1
|
||||
|
||||
def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
if match_bol:
|
||||
initial_state = self.build_opt(m, initial_state, BOL)
|
||||
s = self.build_opt(m, initial_state, EOL)
|
||||
s.add_transition((nl_code, nl_code + 1), final_state)
|
||||
|
||||
RawNewline = _RawNewline()
|
||||
|
||||
|
||||
class SpecialSymbol(RE):
|
||||
"""
|
||||
SpecialSymbol(sym) is an RE which matches the special input
|
||||
symbol |sym|, which is one of BOL, EOL or EOF.
|
||||
"""
|
||||
nullable = 0
|
||||
match_nl = 0
|
||||
sym = None
|
||||
|
||||
def __init__(self, sym):
|
||||
self.sym = sym
|
||||
|
||||
def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
# Sequences 'bol bol' and 'bol eof' are impossible, so only need
|
||||
# to allow for bol if sym is eol
|
||||
if match_bol and self.sym == EOL:
|
||||
initial_state = self.build_opt(m, initial_state, BOL)
|
||||
initial_state.add_transition(self.sym, final_state)
|
||||
|
||||
|
||||
class Seq(RE):
|
||||
"""Seq(re1, re2, re3...) is an RE which matches |re1| followed by
|
||||
|re2| followed by |re3|..."""
|
||||
|
||||
def __init__(self, *re_list):
|
||||
nullable = 1
|
||||
for i in xrange(len(re_list)):
|
||||
re = re_list[i]
|
||||
self.check_re(i, re)
|
||||
nullable = nullable and re.nullable
|
||||
self.re_list = re_list
|
||||
self.nullable = nullable
|
||||
i = len(re_list)
|
||||
match_nl = 0
|
||||
while i:
|
||||
i = i - 1
|
||||
re = re_list[i]
|
||||
if re.match_nl:
|
||||
match_nl = 1
|
||||
break
|
||||
if not re.nullable:
|
||||
break
|
||||
self.match_nl = match_nl
|
||||
|
||||
def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
re_list = self.re_list
|
||||
if len(re_list) == 0:
|
||||
initial_state.link_to(final_state)
|
||||
else:
|
||||
s1 = initial_state
|
||||
n = len(re_list)
|
||||
for i in xrange(n):
|
||||
if i < n - 1:
|
||||
s2 = m.new_state()
|
||||
else:
|
||||
s2 = final_state
|
||||
re = re_list[i]
|
||||
re.build_machine(m, s1, s2, match_bol, nocase)
|
||||
s1 = s2
|
||||
match_bol = re.match_nl or (match_bol and re.nullable)
|
||||
|
||||
def calc_str(self):
|
||||
return "Seq(%s)" % string.join(map(str, self.re_list), ",")
|
||||
|
||||
|
||||
class Alt(RE):
|
||||
"""Alt(re1, re2, re3...) is an RE which matches either |re1| or
|
||||
|re2| or |re3|..."""
|
||||
|
||||
def __init__(self, *re_list):
|
||||
self.re_list = re_list
|
||||
nullable = 0
|
||||
match_nl = 0
|
||||
nullable_res = []
|
||||
non_nullable_res = []
|
||||
i = 1
|
||||
for re in re_list:
|
||||
self.check_re(i, re)
|
||||
if re.nullable:
|
||||
nullable_res.append(re)
|
||||
nullable = 1
|
||||
else:
|
||||
non_nullable_res.append(re)
|
||||
if re.match_nl:
|
||||
match_nl = 1
|
||||
i = i + 1
|
||||
self.nullable_res = nullable_res
|
||||
self.non_nullable_res = non_nullable_res
|
||||
self.nullable = nullable
|
||||
self.match_nl = match_nl
|
||||
|
||||
def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
for re in self.nullable_res:
|
||||
re.build_machine(m, initial_state, final_state, match_bol, nocase)
|
||||
if self.non_nullable_res:
|
||||
if match_bol:
|
||||
initial_state = self.build_opt(m, initial_state, BOL)
|
||||
for re in self.non_nullable_res:
|
||||
re.build_machine(m, initial_state, final_state, 0, nocase)
|
||||
|
||||
def calc_str(self):
|
||||
return "Alt(%s)" % string.join(map(str, self.re_list), ",")
|
||||
|
||||
|
||||
class Rep1(RE):
|
||||
"""Rep1(re) is an RE which matches one or more repetitions of |re|."""
|
||||
|
||||
def __init__(self, re):
|
||||
self.check_re(1, re)
|
||||
self.re = re
|
||||
self.nullable = re.nullable
|
||||
self.match_nl = re.match_nl
|
||||
|
||||
def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
s1 = m.new_state()
|
||||
s2 = m.new_state()
|
||||
initial_state.link_to(s1)
|
||||
self.re.build_machine(m, s1, s2, match_bol or self.re.match_nl, nocase)
|
||||
s2.link_to(s1)
|
||||
s2.link_to(final_state)
|
||||
|
||||
def calc_str(self):
|
||||
return "Rep1(%s)" % self.re
|
||||
|
||||
|
||||
class SwitchCase(RE):
|
||||
"""
|
||||
SwitchCase(re, nocase) is an RE which matches the same strings as RE,
|
||||
but treating upper and lower case letters according to |nocase|. If
|
||||
|nocase| is true, case is ignored, otherwise it is not.
|
||||
"""
|
||||
re = None
|
||||
nocase = None
|
||||
|
||||
def __init__(self, re, nocase):
|
||||
self.re = re
|
||||
self.nocase = nocase
|
||||
self.nullable = re.nullable
|
||||
self.match_nl = re.match_nl
|
||||
|
||||
def build_machine(self, m, initial_state, final_state, match_bol, nocase):
|
||||
self.re.build_machine(m, initial_state, final_state, match_bol,
|
||||
self.nocase)
|
||||
|
||||
def calc_str(self):
|
||||
if self.nocase:
|
||||
name = "NoCase"
|
||||
else:
|
||||
name = "Case"
|
||||
return "%s(%s)" % (name, self.re)
|
||||
|
||||
#
|
||||
# Composite RE constructors
|
||||
# -------------------------
|
||||
#
|
||||
# These REs are defined in terms of the primitive REs.
|
||||
#
|
||||
|
||||
Empty = Seq()
|
||||
Empty.__doc__ = \
|
||||
"""
|
||||
Empty is an RE which matches the empty string.
|
||||
"""
|
||||
Empty.str = "Empty"
|
||||
|
||||
def Str1(s):
|
||||
"""
|
||||
Str1(s) is an RE which matches the literal string |s|.
|
||||
"""
|
||||
result = apply(Seq, tuple(map(Char, s)))
|
||||
result.str = "Str(%s)" % repr(s)
|
||||
return result
|
||||
|
||||
def Str(*strs):
|
||||
"""
|
||||
Str(s) is an RE which matches the literal string |s|.
|
||||
Str(s1, s2, s3, ...) is an RE which matches any of |s1| or |s2| or |s3|...
|
||||
"""
|
||||
if len(strs) == 1:
|
||||
return Str1(strs[0])
|
||||
else:
|
||||
result = apply(Alt, tuple(map(Str1, strs)))
|
||||
result.str = "Str(%s)" % string.join(map(repr, strs), ",")
|
||||
return result
|
||||
|
||||
def Any(s):
|
||||
"""
|
||||
Any(s) is an RE which matches any character in the string |s|.
|
||||
"""
|
||||
#result = apply(Alt, tuple(map(Char, s)))
|
||||
result = CodeRanges(chars_to_ranges(s))
|
||||
result.str = "Any(%s)" % repr(s)
|
||||
return result
|
||||
|
||||
def AnyBut(s):
|
||||
"""
|
||||
AnyBut(s) is an RE which matches any character (including
|
||||
newline) which is not in the string |s|.
|
||||
"""
|
||||
ranges = chars_to_ranges(s)
|
||||
ranges.insert(0, -maxint)
|
||||
ranges.append(maxint)
|
||||
result = CodeRanges(ranges)
|
||||
result.str = "AnyBut(%s)" % repr(s)
|
||||
return result
|
||||
|
||||
AnyChar = AnyBut("")
|
||||
AnyChar.__doc__ = \
|
||||
"""
|
||||
AnyChar is an RE which matches any single character (including a newline).
|
||||
"""
|
||||
AnyChar.str = "AnyChar"
|
||||
|
||||
def Range(s1, s2 = None):
|
||||
"""
|
||||
Range(c1, c2) is an RE which matches any single character in the range
|
||||
|c1| to |c2| inclusive.
|
||||
Range(s) where |s| is a string of even length is an RE which matches
|
||||
any single character in the ranges |s[0]| to |s[1]|, |s[2]| to |s[3]|,...
|
||||
"""
|
||||
if s2:
|
||||
result = CodeRange(ord(s1), ord(s2) + 1)
|
||||
result.str = "Range(%s,%s)" % (s1, s2)
|
||||
else:
|
||||
ranges = []
|
||||
for i in range(0, len(s1), 2):
|
||||
ranges.append(CodeRange(ord(s1[i]), ord(s1[i+1]) + 1))
|
||||
result = apply(Alt, tuple(ranges))
|
||||
result.str = "Range(%s)" % repr(s1)
|
||||
return result
|
||||
|
||||
def Opt(re):
|
||||
"""
|
||||
Opt(re) is an RE which matches either |re| or the empty string.
|
||||
"""
|
||||
result = Alt(re, Empty)
|
||||
result.str = "Opt(%s)" % re
|
||||
return result
|
||||
|
||||
def Rep(re):
|
||||
"""
|
||||
Rep(re) is an RE which matches zero or more repetitions of |re|.
|
||||
"""
|
||||
result = Opt(Rep1(re))
|
||||
result.str = "Rep(%s)" % re
|
||||
return result
|
||||
|
||||
def NoCase(re):
|
||||
"""
|
||||
NoCase(re) is an RE which matches the same strings as RE, but treating
|
||||
upper and lower case letters as equivalent.
|
||||
"""
|
||||
return SwitchCase(re, nocase = 1)
|
||||
|
||||
def Case(re):
|
||||
"""
|
||||
Case(re) is an RE which matches the same strings as RE, but treating
|
||||
upper and lower case letters as distinct, i.e. it cancels the effect
|
||||
of any enclosing NoCase().
|
||||
"""
|
||||
return SwitchCase(re, nocase = 0)
|
||||
|
||||
#
|
||||
# RE Constants
|
||||
#
|
||||
|
||||
Bol = Char(BOL)
|
||||
Bol.__doc__ = \
|
||||
"""
|
||||
Bol is an RE which matches the beginning of a line.
|
||||
"""
|
||||
Bol.str = "Bol"
|
||||
|
||||
Eol = Char(EOL)
|
||||
Eol.__doc__ = \
|
||||
"""
|
||||
Eol is an RE which matches the end of a line.
|
||||
"""
|
||||
Eol.str = "Eol"
|
||||
|
||||
Eof = Char(EOF)
|
||||
Eof.__doc__ = \
|
||||
"""
|
||||
Eof is an RE which matches the end of the file.
|
||||
"""
|
||||
Eof.str = "Eof"
|
||||
|
||||
Executable
+377
@@ -0,0 +1,377 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
#
|
||||
# Scanning an input stream
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
import Errors
|
||||
from Regexps import BOL, EOL, EOF
|
||||
|
||||
class Scanner:
|
||||
"""
|
||||
A Scanner is used to read tokens from a stream of characters
|
||||
using the token set specified by a Plex.Lexicon.
|
||||
|
||||
Constructor:
|
||||
|
||||
Scanner(lexicon, stream, name = '')
|
||||
|
||||
See the docstring of the __init__ method for details.
|
||||
|
||||
Methods:
|
||||
|
||||
See the docstrings of the individual methods for more
|
||||
information.
|
||||
|
||||
read() --> (value, text)
|
||||
Reads the next lexical token from the stream.
|
||||
|
||||
position() --> (name, line, col)
|
||||
Returns the position of the last token read using the
|
||||
read() method.
|
||||
|
||||
begin(state_name)
|
||||
Causes scanner to change state.
|
||||
|
||||
produce(value [, text])
|
||||
Causes return of a token value to the caller of the
|
||||
Scanner.
|
||||
|
||||
"""
|
||||
|
||||
lexicon = None # Lexicon
|
||||
stream = None # file-like object
|
||||
name = ''
|
||||
buffer = ''
|
||||
buf_start_pos = 0 # position in input of start of buffer
|
||||
next_pos = 0 # position in input of next char to read
|
||||
cur_pos = 0 # position in input of current char
|
||||
cur_line = 1 # line number of current char
|
||||
cur_line_start = 0 # position in input of start of current line
|
||||
start_pos = 0 # position in input of start of token
|
||||
start_line = 0 # line number of start of token
|
||||
start_col = 0 # position in line of start of token
|
||||
text = None # text of last token read
|
||||
initial_state = None # Node
|
||||
state_name = '' # Name of initial state
|
||||
queue = None # list of tokens to be returned
|
||||
trace = 0
|
||||
|
||||
def __init__(self, lexicon, stream, name = ''):
|
||||
"""
|
||||
Scanner(lexicon, stream, name = '')
|
||||
|
||||
|lexicon| is a Plex.Lexicon instance specifying the lexical tokens
|
||||
to be recognised.
|
||||
|
||||
|stream| can be a file object or anything which implements a
|
||||
compatible read() method.
|
||||
|
||||
|name| is optional, and may be the name of the file being
|
||||
scanned or any other identifying string.
|
||||
"""
|
||||
self.lexicon = lexicon
|
||||
self.stream = stream
|
||||
self.name = name
|
||||
self.queue = []
|
||||
self.initial_state = None
|
||||
self.begin('')
|
||||
self.next_pos = 0
|
||||
self.cur_pos = 0
|
||||
self.cur_line_start = 0
|
||||
self.cur_char = BOL
|
||||
self.input_state = 1
|
||||
|
||||
def read(self):
|
||||
"""
|
||||
Read the next lexical token from the stream and return a
|
||||
tuple (value, text), where |value| is the value associated with
|
||||
the token as specified by the Lexicon, and |text| is the actual
|
||||
string read from the stream. Returns (None, '') on end of file.
|
||||
"""
|
||||
queue = self.queue
|
||||
while not queue:
|
||||
self.text, action = self.scan_a_token()
|
||||
if action is None:
|
||||
self.produce(None)
|
||||
self.eof()
|
||||
else:
|
||||
value = action.perform(self, self.text)
|
||||
if value is not None:
|
||||
self.produce(value)
|
||||
result = queue[0]
|
||||
del queue[0]
|
||||
return result
|
||||
|
||||
def scan_a_token(self):
|
||||
"""
|
||||
Read the next input sequence recognised by the machine
|
||||
and return (text, action). Returns ('', None) on end of
|
||||
file.
|
||||
"""
|
||||
self.start_pos = self.cur_pos
|
||||
self.start_line = self.cur_line
|
||||
self.start_col = self.cur_pos - self.cur_line_start
|
||||
# if self.trace:
|
||||
# action = self.run_machine()
|
||||
# else:
|
||||
# action = self.run_machine_inlined()
|
||||
action = self.run_machine_inlined()
|
||||
if action:
|
||||
if self.trace:
|
||||
print "Scanner: read: Performing", action, "%d:%d" % (
|
||||
self.start_pos, self.cur_pos)
|
||||
base = self.buf_start_pos
|
||||
text = self.buffer[self.start_pos - base : self.cur_pos - base]
|
||||
return (text, action)
|
||||
else:
|
||||
if self.cur_pos == self.start_pos:
|
||||
if self.cur_char == EOL:
|
||||
self.next_char()
|
||||
if not self.cur_char or self.cur_char == EOF:
|
||||
return ('', None)
|
||||
raise Errors.UnrecognizedInput(self, self.state_name)
|
||||
|
||||
def run_machine(self):
|
||||
"""
|
||||
Run the machine until no more transitions are possible.
|
||||
"""
|
||||
self.state = self.initial_state
|
||||
self.backup_state = None
|
||||
while self.transition():
|
||||
pass
|
||||
return self.back_up()
|
||||
|
||||
def run_machine_inlined(self):
|
||||
"""
|
||||
Inlined version of run_machine for speed.
|
||||
"""
|
||||
state = self.initial_state
|
||||
cur_pos = self.cur_pos
|
||||
cur_line = self.cur_line
|
||||
cur_line_start = self.cur_line_start
|
||||
cur_char = self.cur_char
|
||||
input_state = self.input_state
|
||||
next_pos = self.next_pos
|
||||
buffer = self.buffer
|
||||
buf_start_pos = self.buf_start_pos
|
||||
buf_len = len(buffer)
|
||||
backup_state = None
|
||||
trace = self.trace
|
||||
while 1:
|
||||
if trace: #TRACE#
|
||||
print "State %d, %d/%d:%s -->" % ( #TRACE#
|
||||
state['number'], input_state, cur_pos, repr(cur_char)), #TRACE#
|
||||
# Begin inlined self.save_for_backup()
|
||||
#action = state.action #@slow
|
||||
action = state['action'] #@fast
|
||||
if action:
|
||||
backup_state = (
|
||||
action, cur_pos, cur_line, cur_line_start, cur_char, input_state, next_pos)
|
||||
# End inlined self.save_for_backup()
|
||||
c = cur_char
|
||||
#new_state = state.new_state(c) #@slow
|
||||
new_state = state.get(c, -1) #@fast
|
||||
if new_state == -1: #@fast
|
||||
new_state = c and state.get('else') #@fast
|
||||
if new_state:
|
||||
if trace: #TRACE#
|
||||
print "State %d" % new_state['number'] #TRACE#
|
||||
state = new_state
|
||||
# Begin inlined: self.next_char()
|
||||
if input_state == 1:
|
||||
cur_pos = next_pos
|
||||
# Begin inlined: c = self.read_char()
|
||||
buf_index = next_pos - buf_start_pos
|
||||
if buf_index < buf_len:
|
||||
c = buffer[buf_index]
|
||||
next_pos = next_pos + 1
|
||||
else:
|
||||
discard = self.start_pos - buf_start_pos
|
||||
data = self.stream.read(0x1000)
|
||||
buffer = self.buffer[discard:] + data
|
||||
self.buffer = buffer
|
||||
buf_start_pos = buf_start_pos + discard
|
||||
self.buf_start_pos = buf_start_pos
|
||||
buf_len = len(buffer)
|
||||
buf_index = buf_index - discard
|
||||
if data:
|
||||
c = buffer[buf_index]
|
||||
next_pos = next_pos + 1
|
||||
else:
|
||||
c = ''
|
||||
# End inlined: c = self.read_char()
|
||||
if c == '\n':
|
||||
cur_char = EOL
|
||||
input_state = 2
|
||||
elif not c:
|
||||
cur_char = EOL
|
||||
input_state = 4
|
||||
else:
|
||||
cur_char = c
|
||||
elif input_state == 2:
|
||||
cur_char = '\n'
|
||||
input_state = 3
|
||||
elif input_state == 3:
|
||||
cur_line = cur_line + 1
|
||||
cur_line_start = cur_pos = next_pos
|
||||
cur_char = BOL
|
||||
input_state = 1
|
||||
elif input_state == 4:
|
||||
cur_char = EOF
|
||||
input_state = 5
|
||||
else: # input_state = 5
|
||||
cur_char = ''
|
||||
# End inlined self.next_char()
|
||||
else: # not new_state
|
||||
if trace: #TRACE#
|
||||
print "blocked" #TRACE#
|
||||
# Begin inlined: action = self.back_up()
|
||||
if backup_state:
|
||||
(action, cur_pos, cur_line, cur_line_start,
|
||||
cur_char, input_state, next_pos) = backup_state
|
||||
else:
|
||||
action = None
|
||||
break # while 1
|
||||
# End inlined: action = self.back_up()
|
||||
self.cur_pos = cur_pos
|
||||
self.cur_line = cur_line
|
||||
self.cur_line_start = cur_line_start
|
||||
self.cur_char = cur_char
|
||||
self.input_state = input_state
|
||||
self.next_pos = next_pos
|
||||
if trace: #TRACE#
|
||||
if action: #TRACE#
|
||||
print "Doing", action #TRACE#
|
||||
return action
|
||||
|
||||
# def transition(self):
|
||||
# self.save_for_backup()
|
||||
# c = self.cur_char
|
||||
# new_state = self.state.new_state(c)
|
||||
# if new_state:
|
||||
# if self.trace:
|
||||
# print "Scanner: read: State %d: %s --> State %d" % (
|
||||
# self.state.number, repr(c), new_state.number)
|
||||
# self.state = new_state
|
||||
# self.next_char()
|
||||
# return 1
|
||||
# else:
|
||||
# if self.trace:
|
||||
# print "Scanner: read: State %d: %s --> blocked" % (
|
||||
# self.state.number, repr(c))
|
||||
# return 0
|
||||
|
||||
# def save_for_backup(self):
|
||||
# action = self.state.get_action()
|
||||
# if action:
|
||||
# if self.trace:
|
||||
# print "Scanner: read: Saving backup point at", self.cur_pos
|
||||
# self.backup_state = (
|
||||
# action, self.cur_pos, self.cur_line, self.cur_line_start,
|
||||
# self.cur_char, self.input_state, self.next_pos)
|
||||
|
||||
# def back_up(self):
|
||||
# backup_state = self.backup_state
|
||||
# if backup_state:
|
||||
# (action, self.cur_pos, self.cur_line, self.cur_line_start,
|
||||
# self.cur_char, self.input_state, self.next_pos) = backup_state
|
||||
# if self.trace:
|
||||
# print "Scanner: read: Backing up to", self.cur_pos
|
||||
# return action
|
||||
# else:
|
||||
# return None
|
||||
|
||||
def next_char(self):
|
||||
input_state = self.input_state
|
||||
if self.trace:
|
||||
print "Scanner: next:", " "*20, "[%d] %d" % (input_state, self.cur_pos),
|
||||
if input_state == 1:
|
||||
self.cur_pos = self.next_pos
|
||||
c = self.read_char()
|
||||
if c == '\n':
|
||||
self.cur_char = EOL
|
||||
self.input_state = 2
|
||||
elif not c:
|
||||
self.cur_char = EOL
|
||||
self.input_state = 4
|
||||
else:
|
||||
self.cur_char = c
|
||||
elif input_state == 2:
|
||||
self.cur_char = '\n'
|
||||
self.input_state = 3
|
||||
elif input_state == 3:
|
||||
self.cur_line = self.cur_line + 1
|
||||
self.cur_line_start = self.cur_pos = self.next_pos
|
||||
self.cur_char = BOL
|
||||
self.input_state = 1
|
||||
elif input_state == 4:
|
||||
self.cur_char = EOF
|
||||
self.input_state = 5
|
||||
else: # input_state = 5
|
||||
self.cur_char = ''
|
||||
if self.trace:
|
||||
print "--> [%d] %d %s" % (input_state, self.cur_pos, repr(self.cur_char))
|
||||
|
||||
# def read_char(self):
|
||||
# """
|
||||
# Get the next input character, filling the buffer if necessary.
|
||||
# Returns '' at end of file.
|
||||
# """
|
||||
# next_pos = self.next_pos
|
||||
# buf_index = next_pos - self.buf_start_pos
|
||||
# if buf_index == len(self.buffer):
|
||||
# discard = self.start_pos - self.buf_start_pos
|
||||
# data = self.stream.read(0x1000)
|
||||
# self.buffer = self.buffer[discard:] + data
|
||||
# self.buf_start_pos = self.buf_start_pos + discard
|
||||
# buf_index = buf_index - discard
|
||||
# if not data:
|
||||
# return ''
|
||||
# c = self.buffer[buf_index]
|
||||
# self.next_pos = next_pos + 1
|
||||
# return c
|
||||
|
||||
def position(self):
|
||||
"""
|
||||
Return a tuple (name, line, col) representing the location of
|
||||
the last token read using the read() method. |name| is the
|
||||
name that was provided to the Scanner constructor; |line|
|
||||
is the line number in the stream (1-based); |col| is the
|
||||
position within the line of the first character of the token
|
||||
(0-based).
|
||||
"""
|
||||
return (self.name, self.start_line, self.start_col)
|
||||
|
||||
def begin(self, state_name):
|
||||
"""Set the current state of the scanner to the named state."""
|
||||
self.initial_state = (
|
||||
self.lexicon.get_initial_state(state_name))
|
||||
self.state_name = state_name
|
||||
|
||||
def produce(self, value, text = None):
|
||||
"""
|
||||
Called from an action procedure, causes |value| to be returned
|
||||
as the token value from read(). If |text| is supplied, it is
|
||||
returned in place of the scanned text.
|
||||
|
||||
produce() can be called more than once during a single call to an action
|
||||
procedure, in which case the tokens are queued up and returned one
|
||||
at a time by subsequent calls to read(), until the queue is empty,
|
||||
whereupon scanning resumes.
|
||||
"""
|
||||
if text is None:
|
||||
text = self.text
|
||||
self.queue.append((value, text))
|
||||
|
||||
def eof(self):
|
||||
"""
|
||||
Override this method if you want something to be done at
|
||||
end of file.
|
||||
"""
|
||||
|
||||
# For backward compatibility:
|
||||
setattr(Scanner, "yield", Scanner.produce)
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# Get time in platform-dependent way
|
||||
#
|
||||
|
||||
import os
|
||||
from sys import platform, exit, stderr
|
||||
|
||||
if platform == 'mac':
|
||||
import MacOS
|
||||
def time():
|
||||
return MacOS.GetTicks() / 60.0
|
||||
timekind = "real"
|
||||
elif hasattr(os, 'times'):
|
||||
def time():
|
||||
t = os.times()
|
||||
return t[0] + t[1]
|
||||
timekind = "cpu"
|
||||
else:
|
||||
stderr.write(
|
||||
"Don't know how to get time on platform %s\n" % repr(platform))
|
||||
exit(1)
|
||||
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
# Traditional Regular Expression Syntax
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
from Regexps import *
|
||||
from Errors import PlexError
|
||||
|
||||
class RegexpSyntaxError(PlexError):
|
||||
pass
|
||||
|
||||
def re(s):
|
||||
"""
|
||||
Convert traditional string representation of regular expression |s|
|
||||
into Plex representation.
|
||||
"""
|
||||
return REParser(s).parse_re()
|
||||
|
||||
class REParser:
|
||||
|
||||
def __init__(self, s):
|
||||
self.s = s
|
||||
self.i = -1
|
||||
self.end = 0
|
||||
self.next()
|
||||
|
||||
def parse_re(self):
|
||||
re = self.parse_alt()
|
||||
if not self.end:
|
||||
self.error("Unexpected %s" % repr(self.c))
|
||||
return re
|
||||
|
||||
def parse_alt(self):
|
||||
"""Parse a set of alternative regexps."""
|
||||
re = self.parse_seq()
|
||||
if self.c == '|':
|
||||
re_list = [re]
|
||||
while self.c == '|':
|
||||
self.next()
|
||||
re_list.append(self.parse_seq())
|
||||
re = apply(Alt, tuple(re_list))
|
||||
return re
|
||||
|
||||
def parse_seq(self):
|
||||
"""Parse a sequence of regexps."""
|
||||
re_list = []
|
||||
while not self.end and not self.c in "|)":
|
||||
re_list.append(self.parse_mod())
|
||||
return apply(Seq, tuple(re_list))
|
||||
|
||||
def parse_mod(self):
|
||||
"""Parse a primitive regexp followed by *, +, ? modifiers."""
|
||||
re = self.parse_prim()
|
||||
while not self.end and self.c in "*+?":
|
||||
if self.c == '*':
|
||||
re = Rep(re)
|
||||
elif self.c == '+':
|
||||
re = Rep1(re)
|
||||
else: # self.c == '?'
|
||||
re = Opt(re)
|
||||
self.next()
|
||||
return re
|
||||
|
||||
def parse_prim(self):
|
||||
"""Parse a primitive regexp."""
|
||||
c = self.get()
|
||||
if c == '.':
|
||||
re = AnyBut("\n")
|
||||
elif c == '^':
|
||||
re = Bol
|
||||
elif c == '$':
|
||||
re = Eol
|
||||
elif c == '(':
|
||||
re = self.parse_alt()
|
||||
self.expect(')')
|
||||
elif c == '[':
|
||||
re = self.parse_charset()
|
||||
self.expect(']')
|
||||
else:
|
||||
if c == '\\':
|
||||
c = self.get()
|
||||
re = Char(c)
|
||||
return re
|
||||
|
||||
def parse_charset(self):
|
||||
"""Parse a charset. Does not include the surrounding []."""
|
||||
char_list = []
|
||||
invert = 0
|
||||
if self.c == '^':
|
||||
invert = 1
|
||||
self.next()
|
||||
if self.c == ']':
|
||||
char_list.append(']')
|
||||
self.next()
|
||||
while not self.end and self.c <> ']':
|
||||
c1 = self.get()
|
||||
if self.c == '-' and self.lookahead(1) <> ']':
|
||||
self.next()
|
||||
c2 = self.get()
|
||||
for a in xrange(ord(c1), ord(c2) + 1):
|
||||
char_list.append(chr(a))
|
||||
else:
|
||||
char_list.append(c1)
|
||||
chars = string.join(char_list, "")
|
||||
if invert:
|
||||
return AnyBut(chars)
|
||||
else:
|
||||
return Any(chars)
|
||||
|
||||
def next(self):
|
||||
"""Advance to the next char."""
|
||||
s = self.s
|
||||
i = self.i = self.i + 1
|
||||
if i < len(s):
|
||||
self.c = s[i]
|
||||
else:
|
||||
self.c = ''
|
||||
self.end = 1
|
||||
|
||||
def get(self):
|
||||
if self.end:
|
||||
self.error("Premature end of string")
|
||||
c = self.c
|
||||
self.next()
|
||||
return c
|
||||
|
||||
def lookahead(self, n):
|
||||
"""Look ahead n chars."""
|
||||
j = self.i + n
|
||||
if j < len(self.s):
|
||||
return self.s[j]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def expect(self, c):
|
||||
"""
|
||||
Expect to find character |c| at current position.
|
||||
Raises an exception otherwise.
|
||||
"""
|
||||
if self.c == c:
|
||||
self.next()
|
||||
else:
|
||||
self.error("Missing %s" % repr(c))
|
||||
|
||||
def error(self, mess):
|
||||
"""Raise exception to signal syntax error in regexp."""
|
||||
raise RegexpSyntaxError("Syntax error in regexp %s at position %d: %s" % (
|
||||
repr(self.s), self.i, mess))
|
||||
|
||||
|
||||
|
||||
Executable
+252
@@ -0,0 +1,252 @@
|
||||
#
|
||||
# Plex - Transition Maps
|
||||
#
|
||||
# This version represents state sets direcly as dicts
|
||||
# for speed.
|
||||
#
|
||||
|
||||
from copy import copy
|
||||
import string
|
||||
from sys import maxint
|
||||
from types import TupleType
|
||||
|
||||
class TransitionMap:
|
||||
"""
|
||||
A TransitionMap maps an input event to a set of states.
|
||||
An input event is one of: a range of character codes,
|
||||
the empty string (representing an epsilon move), or one
|
||||
of the special symbols BOL, EOL, EOF.
|
||||
|
||||
For characters, this implementation compactly represents
|
||||
the map by means of a list:
|
||||
|
||||
[code_0, states_0, code_1, states_1, code_2, states_2,
|
||||
..., code_n-1, states_n-1, code_n]
|
||||
|
||||
where |code_i| is a character code, and |states_i| is a
|
||||
set of states corresponding to characters with codes |c|
|
||||
in the range |code_i| <= |c| <= |code_i+1|.
|
||||
|
||||
The following invariants hold:
|
||||
n >= 1
|
||||
code_0 == -maxint
|
||||
code_n == maxint
|
||||
code_i < code_i+1 for i in 0..n-1
|
||||
states_0 == states_n-1
|
||||
|
||||
Mappings for the special events '', BOL, EOL, EOF are
|
||||
kept separately in a dictionary.
|
||||
"""
|
||||
|
||||
map = None # The list of codes and states
|
||||
special = None # Mapping for special events
|
||||
|
||||
def __init__(self, map = None, special = None):
|
||||
if not map:
|
||||
map = [-maxint, {}, maxint]
|
||||
if not special:
|
||||
special = {}
|
||||
self.map = map
|
||||
self.special = special
|
||||
#self.check() ###
|
||||
|
||||
def add(self, event, new_state,
|
||||
TupleType = TupleType):
|
||||
"""
|
||||
Add transition to |new_state| on |event|.
|
||||
"""
|
||||
if type(event) == TupleType:
|
||||
code0, code1 = event
|
||||
i = self.split(code0)
|
||||
j = self.split(code1)
|
||||
map = self.map
|
||||
while i < j:
|
||||
map[i + 1][new_state] = 1
|
||||
i = i + 2
|
||||
else:
|
||||
self.get_special(event)[new_state] = 1
|
||||
|
||||
def add_set(self, event, new_set,
|
||||
TupleType = TupleType):
|
||||
"""
|
||||
Add transitions to the states in |new_set| on |event|.
|
||||
"""
|
||||
if type(event) == TupleType:
|
||||
code0, code1 = event
|
||||
i = self.split(code0)
|
||||
j = self.split(code1)
|
||||
map = self.map
|
||||
while i < j:
|
||||
map[i + 1].update(new_set)
|
||||
i = i + 2
|
||||
else:
|
||||
self.get_special(event).update(new_set)
|
||||
|
||||
def get_epsilon(self):
|
||||
"""
|
||||
Return the mapping for epsilon, or None.
|
||||
"""
|
||||
return self.special.get('')
|
||||
|
||||
def items(self,
|
||||
len = len):
|
||||
"""
|
||||
Return the mapping as a list of ((code1, code2), state_set) and
|
||||
(special_event, state_set) pairs.
|
||||
"""
|
||||
result = []
|
||||
map = self.map
|
||||
else_set = map[1]
|
||||
i = 0
|
||||
n = len(map) - 1
|
||||
code0 = map[0]
|
||||
while i < n:
|
||||
set = map[i + 1]
|
||||
code1 = map[i + 2]
|
||||
if set or else_set:
|
||||
result.append(((code0, code1), set))
|
||||
code0 = code1
|
||||
i = i + 2
|
||||
for event, set in self.special.items():
|
||||
if set:
|
||||
result.append((event, set))
|
||||
return result
|
||||
|
||||
# ------------------- Private methods --------------------
|
||||
|
||||
def split(self, code,
|
||||
len = len, maxint = maxint):
|
||||
"""
|
||||
Search the list for the position of the split point for |code|,
|
||||
inserting a new split point if necessary. Returns index |i| such
|
||||
that |code| == |map[i]|.
|
||||
"""
|
||||
# We use a funky variation on binary search.
|
||||
map = self.map
|
||||
hi = len(map) - 1
|
||||
# Special case: code == map[-1]
|
||||
if code == maxint:
|
||||
return hi
|
||||
# General case
|
||||
lo = 0
|
||||
# loop invariant: map[lo] <= code < map[hi] and hi - lo >= 2
|
||||
while hi - lo >= 4:
|
||||
# Find midpoint truncated to even index
|
||||
mid = ((lo + hi) / 2) & ~1
|
||||
if code < map[mid]:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid
|
||||
# map[lo] <= code < map[hi] and hi - lo == 2
|
||||
if map[lo] == code:
|
||||
return lo
|
||||
else:
|
||||
map[hi:hi] = [code, map[hi - 1].copy()]
|
||||
#self.check() ###
|
||||
return hi
|
||||
|
||||
def get_special(self, event):
|
||||
"""
|
||||
Get state set for special event, adding a new entry if necessary.
|
||||
"""
|
||||
special = self.special
|
||||
set = special.get(event, None)
|
||||
if not set:
|
||||
set = {}
|
||||
special[event] = set
|
||||
return set
|
||||
|
||||
# --------------------- Conversion methods -----------------------
|
||||
|
||||
def __str__(self):
|
||||
map_strs = []
|
||||
map = self.map
|
||||
n = len(map)
|
||||
i = 0
|
||||
while i < n:
|
||||
code = map[i]
|
||||
if code == -maxint:
|
||||
code_str = "-inf"
|
||||
elif code == maxint:
|
||||
code_str = "inf"
|
||||
else:
|
||||
code_str = str(code)
|
||||
map_strs.append(code_str)
|
||||
i = i + 1
|
||||
if i < n:
|
||||
map_strs.append(state_set_str(map[i]))
|
||||
i = i + 1
|
||||
special_strs = {}
|
||||
for event, set in self.special.items():
|
||||
special_strs[event] = state_set_str(set)
|
||||
return "[%s]+%s" % (
|
||||
string.join(map_strs, ","),
|
||||
special_strs
|
||||
)
|
||||
|
||||
# --------------------- Debugging methods -----------------------
|
||||
|
||||
def check(self):
|
||||
"""Check data structure integrity."""
|
||||
if not self.map[-3] < self.map[-1]:
|
||||
print self
|
||||
assert 0
|
||||
|
||||
def dump(self, file):
|
||||
map = self.map
|
||||
i = 0
|
||||
n = len(map) - 1
|
||||
while i < n:
|
||||
self.dump_range(map[i], map[i + 2], map[i + 1], file)
|
||||
i = i + 2
|
||||
for event, set in self.special.items():
|
||||
if set:
|
||||
if not event:
|
||||
event = 'empty'
|
||||
self.dump_trans(event, set, file)
|
||||
|
||||
def dump_range(self, code0, code1, set, file):
|
||||
if set:
|
||||
if code0 == -maxint:
|
||||
if code1 == maxint:
|
||||
k = "any"
|
||||
else:
|
||||
k = "< %s" % self.dump_char(code1)
|
||||
elif code1 == maxint:
|
||||
k = "> %s" % self.dump_char(code0 - 1)
|
||||
elif code0 == code1 - 1:
|
||||
k = self.dump_char(code0)
|
||||
else:
|
||||
k = "%s..%s" % (self.dump_char(code0),
|
||||
self.dump_char(code1 - 1))
|
||||
self.dump_trans(k, set, file)
|
||||
|
||||
def dump_char(self, code):
|
||||
if 0 <= code <= 255:
|
||||
return repr(chr(code))
|
||||
else:
|
||||
return "chr(%d)" % code
|
||||
|
||||
def dump_trans(self, key, set, file):
|
||||
file.write(" %s --> %s\n" % (key, self.dump_set(set)))
|
||||
|
||||
def dump_set(self, set):
|
||||
return state_set_str(set)
|
||||
|
||||
#
|
||||
# State set manipulation functions
|
||||
#
|
||||
|
||||
#def merge_state_sets(set1, set2):
|
||||
# for state in set2.keys():
|
||||
# set1[state] = 1
|
||||
|
||||
def state_set_str(set):
|
||||
state_list = set.keys()
|
||||
str_list = []
|
||||
for state in state_list:
|
||||
str_list.append("S%d" % state.number)
|
||||
return "[%s]" % string.join(str_list, ",")
|
||||
|
||||
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#=======================================================================
|
||||
#
|
||||
# Python Lexical Analyser
|
||||
#
|
||||
#=======================================================================
|
||||
|
||||
"""
|
||||
The Plex module provides lexical analysers with similar capabilities
|
||||
to GNU Flex. The following classes and functions are exported;
|
||||
see the attached docstrings for more information.
|
||||
|
||||
Scanner For scanning a character stream under the
|
||||
direction of a Lexicon.
|
||||
|
||||
Lexicon For constructing a lexical definition
|
||||
to be used by a Scanner.
|
||||
|
||||
Str, Any, AnyBut, AnyChar, Seq, Alt, Opt, Rep, Rep1,
|
||||
Bol, Eol, Eof, Empty
|
||||
|
||||
Regular expression constructors, for building pattern
|
||||
definitions for a Lexicon.
|
||||
|
||||
State For defining scanner states when creating a
|
||||
Lexicon.
|
||||
|
||||
TEXT, IGNORE, Begin
|
||||
|
||||
Actions for associating with patterns when
|
||||
creating a Lexicon.
|
||||
"""
|
||||
|
||||
from Actions import TEXT, IGNORE, Begin
|
||||
from Lexicons import Lexicon, State
|
||||
from Regexps import RE, Seq, Alt, Rep1, Empty, Str, Any, AnyBut, AnyChar, Range
|
||||
from Regexps import Opt, Rep, Bol, Eol, Eof, Case, NoCase
|
||||
from Scanners import Scanner
|
||||
|
||||
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
import sys
|
||||
sys.stderr = sys.stdout
|
||||
|
||||
from TransitionMaps import TransitionMap
|
||||
|
||||
m = TransitionMap()
|
||||
print m
|
||||
|
||||
def add(c, s):
|
||||
print
|
||||
print "adding", repr(c), "-->", repr(s)
|
||||
m.add_transition(c, s)
|
||||
print m
|
||||
print "keys:", m.keys()
|
||||
|
||||
add('a','alpha')
|
||||
add('e', 'eta')
|
||||
add('f', 'foo')
|
||||
add('i', 'iota')
|
||||
add('i', 'imp')
|
||||
add('eol', 'elephant')
|
||||
|
||||
|
||||
|
||||
Executable
+332
@@ -0,0 +1,332 @@
|
||||
# Copyright (C) 2006 Samuel Abels, http://debain.org
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2, as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
import HTMLParser, re, sys
|
||||
from WikiParser import WikiParser
|
||||
from cgi import escape
|
||||
|
||||
class Wiki2Html:
|
||||
def __init__(self):
|
||||
self.html = ''
|
||||
self.buffer = ''
|
||||
self.counter = 0
|
||||
self.in_list = False
|
||||
self.in_numbered_list = False
|
||||
self.in_heading = False
|
||||
self.in_table = False
|
||||
self.in_cell = False
|
||||
self.indent_level = 0
|
||||
self.url_handler = None
|
||||
self.wiki_word_handler = None
|
||||
|
||||
|
||||
def set_url_handler(self, handler):
|
||||
"""
|
||||
Whenever the parser finds a URL in the output, by default it
|
||||
automatically translates it into a clickable link in the HTML output.
|
||||
However, if you want more fine-grained control over which URLs are
|
||||
converted into links, you might want to use this function.
|
||||
It calls the given handler function before a link is added into the
|
||||
HTML. The function is passed the URL and the caption.
|
||||
The handler can the modify the URL and the caption, and pass it
|
||||
back to the parser. Example handler:
|
||||
|
||||
def url_handler(url, caption):
|
||||
return (url, 'My Caption')
|
||||
|
||||
The parser uses the returned values to build a link in the HTML.
|
||||
If URL is None, the item is not linked at all.
|
||||
"""
|
||||
self.url_handler = handler
|
||||
|
||||
|
||||
def set_wiki_word_handler(self, handler):
|
||||
"""
|
||||
Like set_url_handler() but for WikiWords.
|
||||
Example handler:
|
||||
|
||||
def wiki_word_handler(word):
|
||||
return (url, word)
|
||||
|
||||
The parser uses the returned values to build a link in the HTML.
|
||||
If URL is None, the item is not linked at all.
|
||||
"""
|
||||
self.wiki_word_handler = handler
|
||||
|
||||
#onclick="openWindow(getURL('/attachment', {model: 'base.wiki', id: id=$id}), {name : 'Attachments'})
|
||||
def image(self, text):
|
||||
image = text.strip()
|
||||
url = image.split(':')[1]
|
||||
self.buffer += """<a href="/wiki/fullScreen?file='"""+ url +"""'&id=$id">
|
||||
<img height='50%' alt='Please upload Image' width='60%' src="/wiki/getImage?file='"""+ url +"""'&model='base.wiki'&id=$id"/></a><br/>\n"""
|
||||
|
||||
def indent(self, level):
|
||||
self.indent_level = level
|
||||
|
||||
def dedent(self, level):
|
||||
self.indent_level = level
|
||||
|
||||
|
||||
def code_start(self, text):
|
||||
self.buffer += '<pre>'
|
||||
|
||||
|
||||
def code(self, text):
|
||||
self.buffer += escape(text)
|
||||
|
||||
|
||||
def code_end(self, text):
|
||||
self.buffer += '</pre>\n'
|
||||
|
||||
|
||||
def title1_start(self, text):
|
||||
self.buffer += '<h1>'
|
||||
|
||||
|
||||
def title1_end(self, text):
|
||||
self.buffer += '</h1>'
|
||||
|
||||
|
||||
def title2_start(self, text):
|
||||
self.buffer += '<h2>'
|
||||
|
||||
|
||||
def title2_end(self, text):
|
||||
self.buffer += '</h2>'
|
||||
|
||||
|
||||
def title3_start(self, text):
|
||||
self.buffer += '<h3>'
|
||||
|
||||
|
||||
def title3_end(self, text):
|
||||
self.buffer += '</h3>'
|
||||
|
||||
|
||||
def italic_start(self, text):
|
||||
self.buffer += '<i>'
|
||||
|
||||
|
||||
def italic_end(self, text):
|
||||
self.buffer += '</i>'
|
||||
|
||||
|
||||
def bold_start(self, text):
|
||||
self.buffer += '<b>'
|
||||
|
||||
|
||||
def bold_end(self, text):
|
||||
self.buffer += '</b>'
|
||||
|
||||
|
||||
def underline_start(self, text):
|
||||
self.buffer += '<u>'
|
||||
|
||||
|
||||
def underline_end(self, text):
|
||||
self.buffer += '</u>'
|
||||
|
||||
|
||||
def unnumbered_list_start(self, text):
|
||||
self.buffer += '<ul>\n'
|
||||
self.in_list = True
|
||||
|
||||
|
||||
def numbered_list_start(self, text):
|
||||
self.buffer += '<ol>\n'
|
||||
self.in_list = True
|
||||
self.in_numbered_list = True
|
||||
|
||||
|
||||
def list_end(self, text):
|
||||
if self.in_numbered_list:
|
||||
self.buffer += '</ol>\n'
|
||||
self.in_numbered_list = False
|
||||
else:
|
||||
self.buffer += '</ul>\n'
|
||||
self.in_list = False
|
||||
|
||||
|
||||
def list_item_start(self, text):
|
||||
self.buffer += '<li>'
|
||||
|
||||
|
||||
def list_item_end(self, text):
|
||||
self.__flush(True)
|
||||
self.buffer += '</li>\n'
|
||||
|
||||
|
||||
def table_start(self, text):
|
||||
self.in_table = True
|
||||
self.counter = 0
|
||||
self.buffer += '<table>\n'
|
||||
|
||||
|
||||
def heading_start(self, text):
|
||||
self.in_heading = True
|
||||
self.row_start(text)
|
||||
|
||||
|
||||
def row_start(self, text):
|
||||
self.buffer += '<tr>'
|
||||
|
||||
|
||||
def cell_start(self, text):
|
||||
self.in_cell = True
|
||||
attribs = ''
|
||||
if len(text) - 1 > 1:
|
||||
attribs += ' colspan=%i' % (len(text) - 1)
|
||||
if self.in_heading:
|
||||
self.buffer += '<th%s>' % attribs
|
||||
else:
|
||||
self.buffer += '<td%s>' % attribs
|
||||
self.__flush(True)
|
||||
|
||||
|
||||
def cell_end(self, text):
|
||||
# If the cell contains only a hash, replace it by a counter.
|
||||
if self.buffer.strip() == '#':
|
||||
self.counter += 1
|
||||
self.buffer = str(self.counter) + '.'
|
||||
self.__flush(True)
|
||||
self.in_cell = False
|
||||
if self.in_heading:
|
||||
self.html += '</th>'
|
||||
else:
|
||||
self.html += '</td>'
|
||||
self.html += '\n' + (' ' * self.indent_level)
|
||||
|
||||
|
||||
def row_end(self, text):
|
||||
self.buffer += '</tr>\n' + (' ' * self.indent_level)
|
||||
|
||||
|
||||
def heading_end(self, text):
|
||||
self.in_heading = False
|
||||
self.row_end(text)
|
||||
|
||||
|
||||
def table_end(self, text):
|
||||
self.in_table = False
|
||||
self.buffer += '</table>\n'
|
||||
|
||||
|
||||
def text(self, text):
|
||||
self.buffer += escape(text).replace('\n', '<br/>\n')
|
||||
|
||||
|
||||
def newline(self, text):
|
||||
if (self.in_table
|
||||
or self.in_list
|
||||
or (self.buffer[-5:-2] == '</h' and self.buffer[-1] == '>')
|
||||
or (self.buffer[-6:-3] == '</h' and self.buffer[-2] == '>')):
|
||||
self.buffer += '\n'
|
||||
return
|
||||
self.buffer += '<br/>\n'
|
||||
|
||||
|
||||
def link(self, text):
|
||||
text = text[1:-1]
|
||||
pos = text.find('-')
|
||||
if pos == -1:
|
||||
url = text
|
||||
caption = url
|
||||
else:
|
||||
url = text[:pos]
|
||||
caption = text[pos + 1:]
|
||||
if self.url_handler is not None:
|
||||
(url, caption) = self.url_handler(url.strip()+"'", caption)
|
||||
url = escape(url)
|
||||
caption = escape(caption)
|
||||
if url is None:
|
||||
self.buffer += caption
|
||||
else:
|
||||
if url.strip().startswith('http') or url.strip().startswith('www') or url.strip().startswith('https') or url.strip().startswith('frp'):
|
||||
self.buffer += '<a href="%s" target="blank">%s</a>' % (url.strip(), caption)
|
||||
else:
|
||||
self.buffer += '<a href="%s">%s</a>' % ("/wiki?id="+url.strip()+"&title='" + caption.strip() + "'" , caption )
|
||||
|
||||
def wiki_word(self, text):
|
||||
if text.startswith('->'):
|
||||
text = text[2:]
|
||||
url = text
|
||||
caption = text
|
||||
if self.wiki_word_handler is not None:
|
||||
(url, caption) = self.wiki_word_handler("/wiki?id='"+url.strip()+"'", caption)
|
||||
url = escape(url)
|
||||
caption = escape(caption)
|
||||
if url is None:
|
||||
self.buffer += caption
|
||||
else:
|
||||
self.buffer += '<a href="%s">%s</a>' % ("/wiki?id='"+url.strip()+"'", caption)
|
||||
|
||||
|
||||
def not_wiki_word(self, text):
|
||||
self.buffer += escape(text)
|
||||
|
||||
|
||||
def __flush(self, strip = False):
|
||||
if strip:
|
||||
self.html += self.buffer.strip()
|
||||
else:
|
||||
self.html += self.buffer
|
||||
self.buffer = ''
|
||||
|
||||
|
||||
def read(self, filename):
|
||||
infile = open(filename, 'U')
|
||||
parser = WikiParser(infile, filename)
|
||||
nonecount = 0
|
||||
self.html = ''
|
||||
while True:
|
||||
token = parser.read()
|
||||
position = parser.position()
|
||||
if token[0] is None:
|
||||
nonecount += 1 # This is because Plex is broken.
|
||||
if nonecount >= 2:
|
||||
break
|
||||
try:
|
||||
method = getattr(self, token[0])
|
||||
except:
|
||||
method = None
|
||||
if method is not None:
|
||||
method(token[1])
|
||||
infile.close()
|
||||
self.__flush()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import unittest
|
||||
|
||||
class Wiki2HtmlTest(unittest.TestCase):
|
||||
def runTest(self):
|
||||
# Read the entire file into one string.
|
||||
filename = 'markup.txt'
|
||||
infile = open(filename, 'r')
|
||||
in_str = infile.read()
|
||||
infile.close()
|
||||
|
||||
# Parse the file.
|
||||
parser = Wiki2Html()
|
||||
parser.read(filename)
|
||||
html = parser.html
|
||||
print html
|
||||
|
||||
# Now *that's* a poor test, huh? For a better test, look
|
||||
# at the test in Html2Wiki.py.
|
||||
assert len(html) > 1000
|
||||
|
||||
testcase = Wiki2HtmlTest()
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(testcase)
|
||||
Executable
+417
@@ -0,0 +1,417 @@
|
||||
# Copyright (C) 2006 Samuel Abels, http://debain.org
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2, as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
import sys, string
|
||||
sys.path.append('..')
|
||||
from Plex import *
|
||||
|
||||
# Single char definitions.
|
||||
letter = Range('AZaz')
|
||||
digit = Range('09')
|
||||
spaces = Any(" \t\r\n")
|
||||
nl = Str("\n") | Eof
|
||||
not_nl = AnyBut("\r\n")
|
||||
dash = Str('-')
|
||||
colon = Str(':')
|
||||
hash = Str('#')
|
||||
at = Str('@')
|
||||
equal = Str('=')
|
||||
star = Str('*')
|
||||
slash = Str('/')
|
||||
o_bracket = Str('{')
|
||||
c_bracket = Str('}')
|
||||
underscore = Str('_')
|
||||
dot = Str('.')
|
||||
punctuation = Any('.!?,;:')
|
||||
quotes = Any('\'"')
|
||||
|
||||
# Single word definitions.
|
||||
name = letter + Rep(letter | digit)
|
||||
file_name = Rep1(letter | digit | dash | underscore | dot)
|
||||
wiki_force = Str('->') + name
|
||||
wiki_word = Range('AZ') + name + Range('AZ') + name
|
||||
not_w_word = Str('!') + wiki_word
|
||||
indentation = Rep(Str(' ')) | Rep(Str("\t"))
|
||||
variable = letter + Rep(letter | digit | Str('_')) + Rep(letter | digit)
|
||||
proto = Alt(Str('http'), Str('https'), Str('ftp'), Str('mailto'))
|
||||
login = file_name + Opt(colon + file_name)
|
||||
host = name + Rep(letter | digit | dash | underscore | dot) + name
|
||||
path = Opt(slash) + file_name + Rep(slash + file_name) + Opt(slash)
|
||||
arg_value = Rep1(letter | digit | dash | underscore | dot | Str('%'))
|
||||
arg = file_name + equal + Opt(arg_value)
|
||||
args = Str('?') + arg + Rep(Str('&') + arg)
|
||||
path_args = path + Opt(args)
|
||||
url = proto + colon + Str('//') + Opt(login + at) + host + Opt(path_args)
|
||||
code = Str('#Text') + nl
|
||||
code_end = Str('#End') + nl
|
||||
|
||||
# Markup.
|
||||
line = Rep(not_nl) + nl
|
||||
blank_line = indentation + nl
|
||||
words = Rep1(letter | digit | spaces)
|
||||
list_item = Bol + Alt(hash, star) + Str(' ')
|
||||
italic_start = Alt(Bol, spaces) + slash
|
||||
italic_end = slash + Alt(Eol, spaces, punctuation)
|
||||
phrase = Rep1(words | dash | at | punctuation | slash | quotes| hash | Str('$'))
|
||||
title1 = equal + phrase + equal
|
||||
title2 = equal + equal + phrase + equal + equal
|
||||
title3 = equal + equal + equal + phrase + equal + equal + equal
|
||||
heading = Str('#Heading') + nl
|
||||
row = Str('#Row') + nl
|
||||
cell = Rep1(Str('|')) + Str(' ')
|
||||
internal_link = Str('[') + url + Opt(spaces + phrase) + Str(']')
|
||||
external_link = Str('[') + path_args + Opt(spaces + phrase) + Str(']')
|
||||
image = Str('Attach') + colon + Opt(phrase) + nl
|
||||
|
||||
class WikiParser(Scanner):
|
||||
def __init__(self, file, filename):
|
||||
Scanner.__init__(self, self.lexicon, file, filename)
|
||||
self.my_buffer = ''
|
||||
self.indentation_stack = [0]
|
||||
self.bracket_nesting_level = 0
|
||||
self.in_italic = False
|
||||
self.in_bold = False
|
||||
self.in_underline = False
|
||||
self.in_list = False
|
||||
self.in_list_item = False
|
||||
self.in_table = False
|
||||
self.in_heading = False
|
||||
self.in_row = False
|
||||
self.in_cell = False
|
||||
self.begin('indent')
|
||||
|
||||
def _buffer_flush(self):
|
||||
indent = self._get_current_indent_level()
|
||||
#print "_buffer_flush (%i): '%s'" % (indent, self.my_buffer)
|
||||
if self.my_buffer == '':
|
||||
return
|
||||
if self.my_buffer == '\n':
|
||||
self.produce('newline', '\n')
|
||||
else:
|
||||
self.produce('text', self.my_buffer)
|
||||
#print "Flush produced"
|
||||
self.my_buffer = ''
|
||||
|
||||
def _newline_action(self, text):
|
||||
#print '_newline_action'
|
||||
self._buffer_flush()
|
||||
self.produce('newline', text)
|
||||
if self.bracket_nesting_level == 0:
|
||||
self.begin('indent')
|
||||
|
||||
def _indentation_action(self, text):
|
||||
#print '_indentation_action'
|
||||
self.my_buffer += text
|
||||
self._buffer_flush()
|
||||
current_level = self._get_current_indent_level()
|
||||
new_level = len(text)
|
||||
if new_level > current_level:
|
||||
self._indent_to(new_level)
|
||||
elif new_level < current_level:
|
||||
self._dedent_to(new_level)
|
||||
self.begin('')
|
||||
|
||||
def _indent_to(self, new_level):
|
||||
self.indentation_stack.append(new_level)
|
||||
self.produce('indent', new_level)
|
||||
|
||||
def _dedent_to(self, new_level):
|
||||
while new_level < self._get_current_indent_level():
|
||||
self.indentation_stack.pop()
|
||||
self.produce('dedent', self._get_current_indent_level())
|
||||
|
||||
def _get_current_indent_level(self):
|
||||
return self.indentation_stack[-1]
|
||||
|
||||
def _code_start(self, text):
|
||||
#print '_code_start: ' + text
|
||||
self._buffer_flush()
|
||||
self.produce('code_start', text)
|
||||
self.begin('code')
|
||||
|
||||
def _code(self, text):
|
||||
#print '_code: ' + text
|
||||
self.my_buffer += text
|
||||
|
||||
def _code_end(self, text):
|
||||
#print '_code_end: ' + text
|
||||
if self.my_buffer != '':
|
||||
self.produce('code', self.my_buffer)
|
||||
self.my_buffer = ''
|
||||
self.produce('code_end', text)
|
||||
self.begin('')
|
||||
|
||||
def _title1(self, text):
|
||||
#print '_title1: ' + text
|
||||
self._buffer_flush()
|
||||
self.produce('title1_start', text[0])
|
||||
self.my_buffer += text[1:-1]
|
||||
self._buffer_flush()
|
||||
self.produce('title1_end', text[-1])
|
||||
|
||||
def _title2(self, text):
|
||||
#print '_title2: ' + text
|
||||
self._buffer_flush()
|
||||
self.produce('title2_start', text[0:2])
|
||||
self.my_buffer += text[2:-2]
|
||||
self._buffer_flush()
|
||||
self.produce('title2_end', text[-2:])
|
||||
|
||||
def _title3(self, text):
|
||||
#print '_title3: ' + text
|
||||
self._buffer_flush()
|
||||
self.produce('title3_start', text[0:3])
|
||||
self.my_buffer += text[3:-3]
|
||||
self._buffer_flush()
|
||||
self.produce('title3_end', text[-3:])
|
||||
|
||||
def _italic_start(self, text):
|
||||
#print '_italic_start "%s"' % text
|
||||
if text[0] != '/':
|
||||
self.my_buffer += text[0]
|
||||
self._buffer_flush()
|
||||
self.in_italic = True
|
||||
self.produce('italic_start', text[-1])
|
||||
|
||||
def _italic_end(self, text):
|
||||
#print '_italic_end'
|
||||
self._buffer_flush()
|
||||
self.produce('italic_end', text[0])
|
||||
self.in_italic = False
|
||||
self.my_buffer += text[1]
|
||||
|
||||
def _bold(self, text):
|
||||
#print '_bold'
|
||||
self._buffer_flush()
|
||||
if self.in_bold:
|
||||
self.produce('bold_end', text)
|
||||
self.in_bold = False
|
||||
else:
|
||||
self.in_bold = True
|
||||
self.produce('bold_start', text)
|
||||
|
||||
def _underline(self, text):
|
||||
#print '_underline'
|
||||
self._buffer_flush()
|
||||
if self.in_underline:
|
||||
self.produce('underline_end', text)
|
||||
self.in_underline = False
|
||||
else:
|
||||
self.in_underline = True
|
||||
self.produce('underline_start', text)
|
||||
|
||||
def _list_item(self, text):
|
||||
self._buffer_flush()
|
||||
if not self.in_list:
|
||||
if text.startswith('#'):
|
||||
self.produce('numbered_list_start', '')
|
||||
elif text.startswith('*'):
|
||||
self.produce('unnumbered_list_start', '')
|
||||
self.in_list = True
|
||||
if self.in_list_item:
|
||||
self._list_item_end()
|
||||
self.in_list_item = True
|
||||
self.produce('list_item_start')
|
||||
|
||||
def _list_item_end(self):
|
||||
self.in_list_item = False
|
||||
self.produce('list_item_end', '')
|
||||
|
||||
def _list_end(self):
|
||||
if self.in_list_item:
|
||||
self._list_item_end()
|
||||
self.in_list = False
|
||||
self.produce('list_end', '')
|
||||
|
||||
def _heading_start(self, text):
|
||||
self._buffer_flush()
|
||||
if not self.in_table:
|
||||
self.produce('table_start', '')
|
||||
self.in_table = True
|
||||
if self.in_heading:
|
||||
self._heading_end()
|
||||
self.in_heading = True
|
||||
self.produce('heading_start', text[:-1])
|
||||
self._newline_action(text[-1])
|
||||
|
||||
def _row_start(self, text):
|
||||
self._buffer_flush()
|
||||
if not self.in_table:
|
||||
self.produce('table_start', '')
|
||||
self.in_table = True
|
||||
if self.in_heading:
|
||||
self._heading_end()
|
||||
if self.in_row:
|
||||
self._row_end()
|
||||
self.in_row = True
|
||||
self.produce('row_start', text[:-1])
|
||||
self._newline_action(text[-1])
|
||||
|
||||
def _cell_start(self, text):
|
||||
self._buffer_flush()
|
||||
if not self.in_heading and not self.in_row:
|
||||
self._text(text)
|
||||
return
|
||||
if self.in_cell:
|
||||
self._cell_end()
|
||||
self.in_cell = True
|
||||
self.produce('cell_start')
|
||||
|
||||
def _cell_end(self):
|
||||
self.in_cell = False
|
||||
self.produce('cell_end', '')
|
||||
|
||||
def _row_end(self):
|
||||
if self.in_cell:
|
||||
self._cell_end()
|
||||
self.in_row = False
|
||||
self.produce('row_end', '')
|
||||
|
||||
def _heading_end(self):
|
||||
if self.in_cell:
|
||||
self._cell_end()
|
||||
self.in_heading = False
|
||||
self.produce('heading_end', '')
|
||||
|
||||
def _table_end(self):
|
||||
if self.in_heading:
|
||||
self._heading_end()
|
||||
if self.in_row:
|
||||
self._row_end()
|
||||
self.in_table = False
|
||||
self.produce('table_end', '')
|
||||
|
||||
def _link(self, text):
|
||||
self._buffer_flush()
|
||||
self.produce('link', text)
|
||||
|
||||
def _image(self, text):
|
||||
self._buffer_flush()
|
||||
self.produce('image', text)
|
||||
|
||||
def _wiki_word(self, text):
|
||||
self._buffer_flush()
|
||||
self.produce('wiki_word', text)
|
||||
|
||||
def _not_wiki_word(self, text):
|
||||
self._buffer_flush()
|
||||
self.produce('not_wiki_word_intro', text[0])
|
||||
self.produce('not_wiki_word', text[1:])
|
||||
|
||||
def _open_bracket_action(self, text):
|
||||
#print '_open_bracket_action'
|
||||
self.my_buffer += text
|
||||
self.bracket_nesting_level = self.bracket_nesting_level + 1
|
||||
|
||||
def _close_bracket_action(self, text):
|
||||
#print '_close_bracket_action'
|
||||
self.my_buffer += text
|
||||
self.bracket_nesting_level = self.bracket_nesting_level - 1
|
||||
|
||||
def _blank_line(self, text):
|
||||
self._dedent_to(0)
|
||||
self.my_buffer += text
|
||||
if self.in_list:
|
||||
self._list_end()
|
||||
if self.in_table:
|
||||
self._table_end()
|
||||
self._buffer_flush()
|
||||
|
||||
def _text(self, text):
|
||||
#print "Char: '%s'" % text
|
||||
self.my_buffer += text
|
||||
|
||||
def eof(self):
|
||||
#print "EOF"
|
||||
self._blank_line('')
|
||||
|
||||
lexicon = Lexicon([
|
||||
# Handle whitespace and indentation.
|
||||
(nl, _newline_action),
|
||||
State('indent', [
|
||||
(blank_line, _blank_line),
|
||||
(indentation, _indentation_action)
|
||||
]),
|
||||
|
||||
# Preformatted text/code.
|
||||
(code, _code_start),
|
||||
State('code', [
|
||||
(code_end, _code_end),
|
||||
(AnyChar, _code)
|
||||
]),
|
||||
|
||||
# Brackets.
|
||||
(o_bracket, _open_bracket_action),
|
||||
(c_bracket, _close_bracket_action),
|
||||
|
||||
# Styles.
|
||||
(list_item, _list_item),
|
||||
(title1, _title1),
|
||||
(title2, _title2),
|
||||
(title3, _title3),
|
||||
(star, _bold),
|
||||
(underscore, _underline),
|
||||
(italic_start, _italic_start),
|
||||
(italic_end, _italic_end),
|
||||
|
||||
# Tables.
|
||||
(heading, _heading_start),
|
||||
(row, _row_start),
|
||||
(cell, _cell_start),
|
||||
|
||||
# Other.
|
||||
(internal_link, _link),
|
||||
(external_link, _link),
|
||||
(not_w_word, _not_wiki_word),
|
||||
(wiki_word, _wiki_word),
|
||||
(wiki_force, _wiki_word),
|
||||
(AnyChar, _text),
|
||||
(image, _image)
|
||||
])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import unittest
|
||||
|
||||
class ParserTest(unittest.TestCase):
|
||||
def runTest(self):
|
||||
# Read the entire file into one string.
|
||||
filename = 'markup.txt'
|
||||
infile = open(filename, "U")
|
||||
in_text = infile.read()
|
||||
infile.close()
|
||||
|
||||
# Re-open and parse the entire file.
|
||||
infile = open(filename, "r")
|
||||
scanner = WikiParser(infile, filename)
|
||||
content = ''
|
||||
nonecount = 0
|
||||
while True:
|
||||
token = scanner.read()
|
||||
position = scanner.position()
|
||||
if token[0] is None:
|
||||
nonecount += 1 # This is because Plex is broken.
|
||||
if nonecount >= 2:
|
||||
break
|
||||
#print "Token type: %s, Token: '%s'" % (token[0], token[1])
|
||||
if not token[0] in ['indent', 'dedent']:
|
||||
content += token[1]
|
||||
|
||||
# Make sure that every single string was extracted.
|
||||
#print content
|
||||
assert content == in_text
|
||||
|
||||
testcase = ParserTest()
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(testcase)
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
__all__ = ['Wiki2Html',
|
||||
'Html2Wiki']
|
||||
|
||||
from Wiki2Html import Wiki2Html
|
||||
from Html2Wiki import Html2Wiki
|
||||
import wiki
|
||||
#import IndexServer
|
||||
import os
|
||||
|
||||
os.system("python IndexServer.py")
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name" : "Wiki Base System",
|
||||
"version" : "1.0",
|
||||
"author" : "Axelor Sarl",
|
||||
"category" : "Generic Modules/Others",
|
||||
"depends" : ["base", "project"],
|
||||
"init_xml" : [],
|
||||
"demo_xml" : [],
|
||||
"update_xml" : ["wiki_view.xml","wiki_data.xml"],
|
||||
"active": False,
|
||||
"installable": True
|
||||
}
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
|
||||
index_server = ""
|
||||
index_port = 8000
|
||||
|
||||
tiny_port = 8069
|
||||
tiny_host = 'localhost'
|
||||
Executable
+423
@@ -0,0 +1,423 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2004-2006 TINY SPRL. (http://axelor.com) All Rights Reserved.
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsability of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# garantees and support are strongly adviced to contract a Free Software
|
||||
# Service Company
|
||||
#
|
||||
# This program is Free Software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields, osv
|
||||
import time
|
||||
from Wiki2Html import Wiki2Html
|
||||
import xmlrpclib
|
||||
import base64
|
||||
import os
|
||||
from StringIO import StringIO
|
||||
import popen2
|
||||
|
||||
class Tag(osv.osv):
|
||||
_name="base.wiki.tag"
|
||||
_description="Wiki"
|
||||
_rec_name="title"
|
||||
_columns={
|
||||
'title':fields.char('Title',size=128),
|
||||
}
|
||||
Tag()
|
||||
|
||||
class Wiki(osv.osv):
|
||||
_name="base.wiki"
|
||||
_description="Wiki"
|
||||
_rec_name="title"
|
||||
_order = 'model_id'
|
||||
_columns={
|
||||
'title':fields.char('Title', size=128, select=True),
|
||||
'last_modify_by':fields.many2one('res.users',"Last Modify By"),
|
||||
'text_area':fields.text("Content", select=True),
|
||||
'wiki_create_uid':fields.many2one('res.users','Authour', select=True),
|
||||
'wiki_create_date':fields.datetime("Created on", select=True),
|
||||
'wiki_write_date':fields.datetime("Last modified", select=True),
|
||||
'tags':fields.many2many("base.wiki.tag","wiki_tag_many_id","wiki_id","tag_id","Tags", select=True),
|
||||
#'forum_id':fields.one2many('base.wiki.forum','wiki_id','Forum Lines'),
|
||||
'history_id':fields.one2many('base.wiki.history','history_wiki_id','History Lines'),
|
||||
'html':fields.text("Html Data", select=True),
|
||||
'path':fields.char('Page Path',size=128),
|
||||
'model_id': fields.many2one('ir.model', 'Model id', select=True, ondelete='cascade'),
|
||||
'res_id': fields.integer("Record Id"),
|
||||
'minor_edit':fields.boolean('Thisd is a minor edit', select=True),
|
||||
'summary':fields.char('Summary',size=256, select=True),
|
||||
}
|
||||
_defaults = {
|
||||
'wiki_create_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'wiki_create_uid': lambda obj,cr,uid,context: uid,
|
||||
}
|
||||
|
||||
def __init__(self, cr, pool):
|
||||
super(Wiki, self).__init__(cr, pool)
|
||||
self.oldmodel = None
|
||||
|
||||
def read(self, cr, uid, cids, fields=None, context=None, load='_classic_read'):
|
||||
result = super(Wiki, self).read(cr, uid, cids, {}, load='_classic_read')
|
||||
|
||||
if context and context.get('index'):
|
||||
ids = self.search(cr, uid, [('model_id','!=',False), ('id','in',cids)])
|
||||
res = self.read(cr, uid, ids)
|
||||
|
||||
buffer = ''
|
||||
for rs in res:
|
||||
if self.oldmodel != rs['model_id'][1] or self.oldmodel == None:
|
||||
self.oldmodel = rs['model_id'][1]
|
||||
buffer+= '\n==' + self.pool.get('ir.model').browse(cr, uid, rs['model_id'][0]).name + '==\n'
|
||||
|
||||
rec_url = '/form/view?model=' + rs['model_id'][1] + "&id=" + str(rs['res_id'])
|
||||
edit_url = '/form/edit?model=' + rs['model_id'][1] + "&id=" + str(rs['res_id'])
|
||||
buffer+= '#Row\n'
|
||||
buffer+= '| # | ' + rs['title'] + ' | [' + rs['path'] + ' - wiki]' + ' | [' + rec_url + ' - Browse ] | [' + edit_url + ' - Edit ]\n'
|
||||
|
||||
ids = self.search(cr, uid, [('model_id','=',False), ('id','in',cids)])
|
||||
res = self.read(cr, uid, ids)
|
||||
|
||||
if res:
|
||||
buffer+= '\n==Other Pages==\n'
|
||||
for rs in res:
|
||||
|
||||
buffer+= '* [' + rs['path'] + ' - ' + rs['title'] + ']\n'
|
||||
|
||||
return [{'html':self.Wiki2Html(buffer)}]
|
||||
else:
|
||||
return result
|
||||
|
||||
def Wiki2Html(self, wiki):
|
||||
fileName = 'data.txt'
|
||||
file = open(fileName, 'w')
|
||||
file.write(wiki+'\n\n')
|
||||
file.close()
|
||||
|
||||
parser = Wiki2Html()
|
||||
parser.read(fileName)
|
||||
return parser.html
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if vals.get('text_area'):
|
||||
vals['html'] = self.Wiki2Html(vals['text_area'])
|
||||
else:
|
||||
vals['text_area'] = 'Your text goes here'
|
||||
vals['html'] = 'You have not create the page.'
|
||||
if not vals.has_key('minor_edit'):
|
||||
return super(Wiki,self).create(cr, uid, vals, context)
|
||||
vals['history_id']=[[0,0,{'minor_edit':vals['minor_edit'],'text_area':vals['text_area'],'modify_by':uid,'summary':vals['summary']}]]
|
||||
return super(Wiki,self).create(cr, uid, vals, context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
vals['wiki_write_date']=time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
vals['last_modify_by']=uid
|
||||
wiki_data=self.read(cr,uid,ids,['minor_edit','summary'])[0]
|
||||
if vals.get('text_area'):
|
||||
vals['html'] = self.Wiki2Html(vals['text_area'])
|
||||
if vals.has_key('minor_edit') and vals.has_key('summary'):
|
||||
vals['history_id']=[[0,0,{'minor_edit':vals['minor_edit'],'text_area':vals['text_area'],'modify_by':uid,'summary':vals['summary']}]]
|
||||
elif vals.has_key('minor_edit'):
|
||||
vals['history_id']=[[0,0,{'minor_edit':vals['minor_edit'],'text_area':vals['text_area'],'modify_by':uid,'summary':wiki_data['summary']}]]
|
||||
elif vals.has_key('summary'):
|
||||
vals['history_id']=[[0,0,{'minor_edit':wiki_data['summary'],'text_area':vals['text_area'],'modify_by':uid,'summary':vals['summary']}]]
|
||||
else:
|
||||
vals['history_id']=[[0,0,{'minor_edit':wiki_data['minor_edit'],'text_area':vals['text_area'],'modify_by':uid,'summary':wiki_data['summary']}]]
|
||||
return super(Wiki,self).write(cr, uid, ids, vals, context)
|
||||
Wiki()
|
||||
|
||||
#class Forum(osv.osv):
|
||||
# _name="base.wiki.forum"
|
||||
# _description="Wiki Forum"
|
||||
# _rec_name="title"
|
||||
# _columns={
|
||||
# 'title':fields.char('Title',size=128),
|
||||
# 'replies':fields.integer('Replies'),
|
||||
# 'wiki_authour':fields.many2one('res.users','Created By'),
|
||||
# 'last_post':fields.datetime("Last Post",readonly=True),
|
||||
# 'by':fields.many2one('res.users','By',readonly=True),
|
||||
# 'discussion_lines':fields.one2many('base.wiki.discussion','forum_id','Discussion Lines'),
|
||||
# 'wiki_id':fields.many2one('base.wiki','Wiki Id')
|
||||
# }
|
||||
#Forum()
|
||||
#
|
||||
#class Discussion(osv.osv):
|
||||
# _name="base.wiki.discussion"
|
||||
# _description="Wiki Discussion"
|
||||
# _rec_name="wiki_authour"
|
||||
# _columns={
|
||||
# 'wiki_authour':fields.many2one('res.users','Authour',readonly=True),
|
||||
# 'posted_date':fields.datetime("Posted Date",readonly=True),
|
||||
# 'message':fields.text('Message'),
|
||||
# 'forum_id':fields.many2one('base.wiki.forum','Forum Id')
|
||||
# }
|
||||
#Discussion()
|
||||
|
||||
class History(osv.osv):
|
||||
_name="base.wiki.history"
|
||||
_description="Wiki History"
|
||||
_rec_name="date_time"
|
||||
_order = 'id DESC'
|
||||
_columns={
|
||||
'date_time':fields.datetime("Date",select=True),
|
||||
'text_area':fields.text("Text area",select=True),
|
||||
'minor_edit':fields.boolean('This is a major edit ?',select=True),
|
||||
'summary':fields.char('Summary',size=256, select=True),
|
||||
'modify_by':fields.many2one('res.users',"Modify By", select=True),
|
||||
'hist_write_date':fields.datetime("Last modified", select=True),
|
||||
'history_wiki_id':fields.many2one('base.wiki','Wiki Id', select=True)
|
||||
}
|
||||
_defaults = {
|
||||
'hist_write_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'modify_by': lambda obj,cr,uid,context: uid,
|
||||
}
|
||||
|
||||
def getDiff(self, cr, uid, v1, v2, context={}):
|
||||
import difflib
|
||||
|
||||
history_pool = self.pool.get('base.wiki.history')
|
||||
|
||||
text1 = history_pool.read(cr, uid, [v1], ['text_area'])[0]['text_area']
|
||||
text2 = history_pool.read(cr, uid, [v2], ['text_area'])[0]['text_area']
|
||||
|
||||
line1 = text1.splitlines(1)
|
||||
line2 = text2.splitlines(1)
|
||||
|
||||
diff = difflib.HtmlDiff()
|
||||
|
||||
return diff.make_file(line1, line2, "Revision-%s" % (v1), "Revision-%s" % (v2), context=False)
|
||||
|
||||
History()
|
||||
|
||||
from StringIO import StringIO
|
||||
from HTMLParser import HTMLParser
|
||||
|
||||
class IndexLine(osv.osv):
|
||||
_name="base.index.line"
|
||||
_description="Index Lines"
|
||||
_columns={
|
||||
'name':fields.text('Content', select=True),
|
||||
'active':fields.boolean('Active', select=True),
|
||||
'model':fields.char('Resource',size=256, select=True),
|
||||
'res_id':fields.integer('Resource Id', select=True),
|
||||
}
|
||||
_defaults = {
|
||||
'active': lambda *a: True,
|
||||
}
|
||||
|
||||
def __init__(self, cr , pool):
|
||||
super(IndexLine, self).__init__(cr, pool)
|
||||
|
||||
def reIndex(self, cr, uid, ids, context={}):
|
||||
password = self.pool.get('res.users').browse(cr, uid, uid).password
|
||||
self.server.init(cr.dbname, uid, password, True)
|
||||
self.server.open()
|
||||
|
||||
self.Index(cr, uid, context)
|
||||
|
||||
self.server.close()
|
||||
return True
|
||||
|
||||
def Index(self, cr, uid, ids, context={}):
|
||||
|
||||
entry_pool = self.pool.get('base.index.line')
|
||||
eids = entry_pool.search(cr, uid, [])
|
||||
# entry_pool.unlink(cr, uid, eids)
|
||||
# cr.commit()
|
||||
|
||||
model_pool = self.pool.get('ir.model')
|
||||
field_pool = self.pool.get('ir.model.fields')
|
||||
|
||||
ids = model_pool.search(cr, uid, [('model','!=','base.index.line')])
|
||||
models = model_pool.read(cr, uid, ids, ['id', 'model'])
|
||||
|
||||
for mod in models:
|
||||
res_ids = None
|
||||
|
||||
if str(mod['model']).startswith('workflow'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('ir'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('base.index.line'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('res.currency'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('base.wiki'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('report'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('account_analytic_analysis.summary.user'):
|
||||
continue
|
||||
|
||||
if str(mod['model']).startswith('hr_timesheet_invoice.factor'):
|
||||
continue
|
||||
|
||||
try:
|
||||
fids = field_pool.search(cr, 1, [('model_id','=',mod['id'])])
|
||||
fdata = field_pool.read(cr, uid, fids, ['name','relation', 'ttype'])
|
||||
except Exception, e:
|
||||
continue
|
||||
#
|
||||
# fkeys = {}
|
||||
# tkeys = {}
|
||||
# for fd in fdata:
|
||||
# fkeys[fd['name']] = fd['relation']
|
||||
# tkeys[fd['name']] = fd['ttype']
|
||||
|
||||
res_pool = None
|
||||
res_ids = None
|
||||
|
||||
res_pool = self.pool.get(mod['model'])
|
||||
|
||||
res_datas = None
|
||||
|
||||
try:
|
||||
res_ids = res_pool.search(cr, 1, [])
|
||||
res_datas = res_pool.read(cr, 1, res_ids)
|
||||
#cr.execute("select * from %s" % ( mod['model'].replace('.','_')))
|
||||
#res_datas = cr.dictfetchall()
|
||||
except Exception, e:
|
||||
print e
|
||||
continue
|
||||
|
||||
print 'Indexing : ', mod['model'], ' Auto Status : '
|
||||
|
||||
if res_datas:
|
||||
for res_data in res_datas:
|
||||
|
||||
exist_ids = entry_pool.search(cr, uid, [('res_id','=',res_data['id']),('model','=',mod['model'])])
|
||||
if exist_ids.__len__() > 0:
|
||||
continue
|
||||
|
||||
final_res = ''
|
||||
#
|
||||
# for col in res_data:
|
||||
# if fkeys.has_key(col) and fkeys[col] != 'NULL':
|
||||
# if tkeys.has_key(col) and tkeys[col] == 'one2many':
|
||||
# rel_pool = self.pool.get(fkeys.get(col))
|
||||
# try:
|
||||
# foreign_key = res_pool._columns[col]._fields_id
|
||||
# except Exception, e:
|
||||
# print e
|
||||
# continue
|
||||
# fkids = rel_pool.search(cr, 1, [(foreign_key, '=', res_data['id'])])
|
||||
# if fkids.__len__() > 0:
|
||||
# fkdata = rel_pool.read(cr, 1, fkids)
|
||||
# for fkd in fkdata:
|
||||
# final_res += ' '.join(map(str,fkd.values()))
|
||||
|
||||
final_res += ' '.join(map(str,res_data.values()))
|
||||
|
||||
entry_pool.create(cr, uid, {
|
||||
'model':mod['model'],
|
||||
'res_id':res_data['id'],
|
||||
'name': final_res
|
||||
})
|
||||
cr.commit()
|
||||
|
||||
return True
|
||||
|
||||
def createIndex(self, cr, uid, ids, context={}):
|
||||
return self.Index(cr, uid, ids, context)
|
||||
|
||||
def getResult(self, cr, uid, key, reSearch=True, context = {}):
|
||||
|
||||
# password = self.pool.get('res.users').browse(cr, uid, uid).password
|
||||
# self.server.init(cr.dbname, uid, password, True)
|
||||
|
||||
if context and context.get('read_all') and key:
|
||||
try:
|
||||
#res = self.server.search(key)
|
||||
ids = self.search(cr, uid, [('name','ilike',key)])
|
||||
res = self.read(cr, uid, ids, ['model', 'res_id'])
|
||||
except Exception, e:
|
||||
raise osv.except_osv('Search Error !!', e.faultString)
|
||||
|
||||
trs = {}
|
||||
for rs in res:
|
||||
model = rs.get('model')
|
||||
id = rs.get('res_id')
|
||||
if trs.has_key(model):
|
||||
olds = trs[model]
|
||||
olds.append(id)
|
||||
else:
|
||||
trs[model] = [id]
|
||||
|
||||
res = trs
|
||||
if res:
|
||||
buffer = ''
|
||||
for mod in res.keys():
|
||||
sbuff = ''
|
||||
obj_pool = self.pool.get(mod)
|
||||
header = False
|
||||
|
||||
for id in res[mod]:
|
||||
name = None
|
||||
try:
|
||||
name = obj_pool.name_get(cr, 1, [id])[0][1]
|
||||
obj_pool.read(cr, uid, [id])
|
||||
header = True
|
||||
except Exception, e:
|
||||
header = False
|
||||
continue
|
||||
|
||||
rec_url = '/form/view?model=' + mod + "&id=" + str(id)
|
||||
edit_url = '/form/edit?model=' + mod + "&id=" + str(id)
|
||||
sbuff+= '#Row\n'
|
||||
name = name.replace('/',' ')
|
||||
name = name.replace('*',' ')
|
||||
name = name.replace('_',' ')
|
||||
sbuff+= '| # | ' + name + ' | [' + mod + '/' + str(id) + ' - Wiki]' + ' | [' + rec_url + ' - Browse ] | [' + edit_url + ' - Edit ]\n'
|
||||
|
||||
if header == True:
|
||||
mid = self.pool.get('ir.model').search(cr, uid, [('model','=',mod)])[0]
|
||||
mod_data = self.pool.get('ir.model').browse(cr, uid, mid).name
|
||||
sbuff = '\n==' + mod_data + '==\n' + sbuff
|
||||
|
||||
buffer += sbuff
|
||||
html = self.pool.get('base.wiki').Wiki2Html(buffer)
|
||||
html = html.replace('$s', '<strike>')
|
||||
html = html.replace('$es', '</strike>')
|
||||
return [{'html':html}]
|
||||
# else:
|
||||
# #TODO: need to create an index on the new records
|
||||
# if reSearch:
|
||||
# self.Index(cr, uid, context)
|
||||
# self.getResult(cr, uid, key, False, context)
|
||||
|
||||
return None
|
||||
|
||||
IndexLine()
|
||||
|
||||
class Project(osv.osv):
|
||||
_inherit = 'project.project'
|
||||
_name = 'project.project'
|
||||
_columns = {
|
||||
'wiki':fields.many2one('base.wiki', 'Wiki'),
|
||||
'dwiki':fields.many2one('base.wiki', 'Developer Wiki'),
|
||||
}
|
||||
Project()
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<terp>
|
||||
<data noupdate="1">
|
||||
<record model="base.wiki" id="base_wiki_home">
|
||||
<field name="title">Wiki Home</field>
|
||||
<field name="path">home</field>
|
||||
<field name="minor_edit">False</field>
|
||||
<field name="summary"></field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</terp>
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
<?xml version="1.0"?>
|
||||
<terp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_wiki_tree">
|
||||
<field name="name">base.wiki.tree</field>
|
||||
<field name="model">base.wiki</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Wiki">
|
||||
<field name="title"/>
|
||||
<field name="model_id" string="Authour"/>
|
||||
<field name="res_id" string="Created on"/>
|
||||
<field name="path"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_wiki_form">
|
||||
<field name="name">base.wiki.form</field>
|
||||
<field name="model">base.wiki</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Wiki">
|
||||
<notebook>
|
||||
<page string="Article">
|
||||
<separator string="Wiki Title" colspan="4" />
|
||||
<field name="title" nolabel="1" colspan="4" search="1"/>
|
||||
<separator string="Wiki Content" colspan="4" />
|
||||
<field name="text_area" nolabel="1" colspan="4" search="1"/>
|
||||
<field name="minor_edit" search="1"/>
|
||||
<field name="summary" search="1"/>
|
||||
</page>
|
||||
<page string="Html Page">
|
||||
<separator string="Html Page" colspan="4" />
|
||||
<field name="html" nolabel="1" colspan="4" widget="html_tag"/>
|
||||
</page>
|
||||
<page string="Tags">
|
||||
<separator string="Tags" colspan="4" />
|
||||
<field name="tags" nolabel="1" colspan="4" search="1"/>
|
||||
</page>
|
||||
<page string="History">
|
||||
<field name="history_id" mode="tree,form" colspan="4" nolabel="1" select="1">
|
||||
<tree string="History">
|
||||
<field name="text_area" />
|
||||
<field name="modify_by" />
|
||||
</tree>
|
||||
<form string="History">
|
||||
<field name="text_area" colspan="4"/>
|
||||
<field name="modify_by" />
|
||||
<field name="minor_edit" />
|
||||
<field name="summary" colspan="4"/>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_wiki">
|
||||
<field name="name">Wiki Pages</field>
|
||||
<field name="res_model">base.wiki</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" ref="view_wiki_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Wiki System/Wiki Pages" id="menu_action_axelor_wiki" action="action_wiki"/>
|
||||
|
||||
|
||||
<record model="ir.ui.view" id="view_wiki_forum_tree">
|
||||
<field name="name">base.wiki.forum.tree</field>
|
||||
<field name="model">base.wiki.forum</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="wiki forum">
|
||||
<field name="title"/>
|
||||
<field name="replies"/>
|
||||
<field name="wiki_authour"/>
|
||||
<field name="last_post"/>
|
||||
<field name="by"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_wiki_history_tree">
|
||||
<field name="name">base.wiki.history.tree</field>
|
||||
<field name="model">base.wiki.history</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree srting="Wiki History">
|
||||
<field name="date_time"/>
|
||||
<field name="text_area"/>
|
||||
<field name="minor_edit"/>
|
||||
<field name="summary"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="wiki_history_form">
|
||||
<field name="name">base.wiki.history.form</field>
|
||||
<field name="model">base.wiki.history</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form srting="Wiki History">
|
||||
<field name="summary" select="1" colspan="4"/>
|
||||
<field name="text_area" select="1" colspan="4"/>
|
||||
<field name="date_time" select="1"/>
|
||||
<field name="minor_edit" select="1"/>
|
||||
<field name="history_wiki_id"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_history">
|
||||
<field name="name">Wiki History</field>
|
||||
<field name="res_model">base.wiki.history</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" ref="wiki_history_form"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Wiki System/History" id="menu_action_history" action="action_history"/>
|
||||
|
||||
|
||||
<!-- <record model="ir.ui.view" id="base_index_server_form">-->
|
||||
<!-- <field name="name">base.index.server.form</field>-->
|
||||
<!-- <field name="model">base.index.server</field>-->
|
||||
<!-- <field name="type">form</field>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <form srting="Index Server">-->
|
||||
<!-- <field name="name" select="1" colspan="4"/>-->
|
||||
<!-- <field name="index_line" select="1" colspan="4">-->
|
||||
<!-- <tree>-->
|
||||
<!-- <field name="name"/>-->
|
||||
<!-- <field name="res_id"/>-->
|
||||
<!-- </tree>-->
|
||||
<!-- </field>-->
|
||||
<!-- <field name="active"/>-->
|
||||
<!-- <newline/>-->
|
||||
<!-- <field name="last_indexed"/>-->
|
||||
<!-- -->
|
||||
<!-- <button name="reIndex" type="object" string="Create New Index"/>-->
|
||||
<!-- <button name="createIndex" type="object" string="Index New Records"/>-->
|
||||
<!-- -->
|
||||
<!-- </form>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
<!---->
|
||||
<!-- <record model="ir.actions.act_window" id="action_base_index_server">-->
|
||||
<!-- <field name="name">Index Server</field>-->
|
||||
<!-- <field name="res_model">base.index.server</field>-->
|
||||
<!-- <field name="view_type">form</field>-->
|
||||
<!-- <field name="view_mode">form,tree</field>-->
|
||||
<!-- <field name="view_id" ref="base_index_server_form"/>-->
|
||||
<!-- </record>-->
|
||||
<!---->
|
||||
<!-- <menuitem name="Administration/Configuration/Servers/Index Srver" id="menu_action_base_index_server" action="action_base_index_server"/>-->
|
||||
|
||||
<record model="ir.ui.view" id="action_base_index_line_form">
|
||||
<field name="name">base.index.line.form</field>
|
||||
<field name="model">base.index.line</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form srting="Index Line">
|
||||
<field name="model" select="1"/>
|
||||
<field name="res_id" select="1"/>
|
||||
<field name="name" colspan="4"/>
|
||||
<label string=""/>
|
||||
<button string="Create Index" name="createIndex" type="object"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="action_base_index_line_tree">
|
||||
<field name="name">base.index.line.tree</field>
|
||||
<field name="model">base.index.line</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree srting="Index Lines">
|
||||
<field name="model" select="1"/>
|
||||
<field name="res_id" select="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_base_index_line">
|
||||
<field name="name">Index Server</field>
|
||||
<field name="res_model">base.index.line</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="action_base_index_line_form"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Wiki System/Configuration/Index Lines" id="menu_action_base_index_line" action="action_base_index_line"/>
|
||||
|
||||
|
||||
<!--<record model="ir.ui.view" id="base_wiki_test_form">
|
||||
<field name="name">base.wiki.test.form</field>
|
||||
<field name="model">base.wiki.test</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form srting="Field Based Wiki">
|
||||
<notebook>
|
||||
<page string="Object Data">
|
||||
<field name="name" select="1"/>
|
||||
</page>
|
||||
<page string="Customer Wiki">
|
||||
<field name="wiki" select="1" colspan="4" nolabel="1" widget="wiki"/>
|
||||
</page>
|
||||
<page string="Developer Wiki">
|
||||
<field name="dwiki" select="1" colspan="4" nolabel="1" widget="wiki"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_base_wiki_test_form">
|
||||
<field name="name">Wiki Test</field>
|
||||
<field name="res_model">base.wiki.test</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" ref="base_wiki_test_form"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Wiki System/Wiki Test" id="menu_action_base_wiki_test_form" action="action_base_wiki_test_form"/>
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<record model="ir.ui.view" id="edit_project">
|
||||
<field name="name">project.project.form</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.edit_project"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<notebook position="inside">
|
||||
<page string="Functional Document">
|
||||
<field name="wiki" select="1" colspan="4" nolabel="1" widget="wiki"/>
|
||||
</page>
|
||||
<page string="Technical Document">
|
||||
<field name="dwiki" select="1" colspan="4" nolabel="1" widget="wiki"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</terp>
|
||||
Reference in New Issue
Block a user