checking in work to deepen migration to new mediawikiutils

This commit is contained in:
Nathan TeBlunthuis 2018-12-11 00:31:50 -08:00
parent 7d62ff9fb7
commit 0c2d72b881
2 changed files with 398 additions and 252 deletions

View File

@ -16,6 +16,45 @@ from io import StringIO
# wikia and wikipedia data DONE # wikia and wikipedia data DONE
# malformed xmls DONE # malformed xmls DONE
# class Test_Persistence_Bug(unittest.TestCase):
# def setUp(self):
# if not os.path.exists("test_output"):
# os.mkdir("test_output")
# self.wiki = 'enwiki-test'
# self.wikiq_out_name = self.wiki + ".tsv"
# self.test_output_dir = os.path.join(".", "test_output")
# self.call_output = os.path.join(self.test_output_dir, self.wikiq_out_name)
# self.infile = "{0}.xml".format(self.wiki)
# self.base_call = "../wikiq {0} -o {1}"
# self.input_dir = "dumps"
# self.input_file = os.path.join(".", self.input_dir,self.infile)
# self.baseline_output_dir = "baseline_output"
# def test_segment_persistence(self):
# test_filename = "sequence-" + self.wikiq_out_name
# test_file = os.path.join(self.test_output_dir, test_filename)
# if os.path.exists(test_file):
# os.remove(test_file)
# call = self.base_call.format(self.input_file, self.test_output_dir)
# call = call + " --url-encode --persistence sequence --collapse-user"
# print(os.path.abspath('.'))
# print(call)
# proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
# proc.wait()
# copyfile(self.call_output, test_file)
# baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
# # as a test let's make sure that we get equal data frames
# test = pd.read_table(test_file)
# baseline = pd.read_table(baseline_file)
# assert_frame_equal(test,baseline)
class Test_Wikipedia(unittest.TestCase): class Test_Wikipedia(unittest.TestCase):
def setUp(self): def setUp(self):
if not os.path.exists("test_output"): if not os.path.exists("test_output"):
@ -47,6 +86,7 @@ class Test_Wikipedia(unittest.TestCase):
baseline_file = os.path.join(".", self.baseline_output_dir, test_filename) baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
# as a test let's make sure that we get equal data frames # as a test let's make sure that we get equal data frames
test = pd.read_table(test_file) test = pd.read_table(test_file)
baseline = pd.read_table(baseline_file) baseline = pd.read_table(baseline_file)
assert_frame_equal(test,baseline) assert_frame_equal(test,baseline)
@ -63,7 +103,7 @@ class Test_Wikipedia(unittest.TestCase):
print(call) print(call)
proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True) proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
proc.wait() proc.wait()
copyfile(self.call_output, test_file) # copyfile(self.call_output, test_file)
baseline_file = os.path.join(os.path.abspath("."), self.baseline_output_dir, test_filename) baseline_file = os.path.join(os.path.abspath("."), self.baseline_output_dir, test_filename)
# as a test let's make sure that we get equal data frames # as a test let's make sure that we get equal data frames
@ -74,191 +114,192 @@ class Test_Wikipedia(unittest.TestCase):
assert_frame_equal(test,baseline) assert_frame_equal(test,baseline)
class Test_Basic(unittest.TestCase): # class Test_Basic(unittest.TestCase):
def setUp(self): # def setUp(self):
if not os.path.exists("test_output"): # if not os.path.exists("test_output"):
os.mkdir("test_output") # os.mkdir("test_output")
self.wiki = 'sailormoon' # self.wiki = 'sailormoon'
self.wikiq_out_name = self.wiki + ".tsv" # self.wikiq_out_name = self.wiki + ".tsv"
self.test_output_dir = os.path.join(".", "test_output") # self.test_output_dir = os.path.join(".", "test_output")
self.call_output = os.path.join(self.test_output_dir, self.wikiq_out_name) # self.call_output = os.path.join(self.test_output_dir, self.wikiq_out_name)
self.infile = "{0}.xml.7z".format(self.wiki) # self.infile = "{0}.xml.7z".format(self.wiki)
self.base_call = "../wikiq {0} -o {1}" # self.base_call = "../wikiq {0} -o {1}"
self.input_dir = "dumps" # self.input_dir = "dumps"
self.input_file = os.path.join(".", self.input_dir,self.infile) # self.input_file = os.path.join(".", self.input_dir,self.infile)
self.baseline_output_dir = "baseline_output" # self.baseline_output_dir = "baseline_output"
def test_noargs(self): # def test_noargs(self):
test_filename = "noargs_" + self.wikiq_out_name # test_filename = "noargs_" + self.wikiq_out_name
test_file = os.path.join(self.test_output_dir, test_filename) # test_file = os.path.join(self.test_output_dir, test_filename)
if os.path.exists(test_file): # if os.path.exists(test_file):
os.remove(test_file) # os.remove(test_file)
call = self.base_call.format(self.input_file, self.test_output_dir) # call = self.base_call.format(self.input_file, self.test_output_dir)
proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True) # proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
proc.wait() # proc.wait()
copyfile(self.call_output, test_file) # copyfile(self.call_output, test_file)
baseline_file = os.path.join(".", self.baseline_output_dir, test_filename) # baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
test = pd.read_table(test_file) # test = pd.read_table(test_file)
baseline = pd.read_table(baseline_file) # baseline = pd.read_table(baseline_file)
assert_frame_equal(test,baseline) # assert_frame_equal(test,baseline)
def test_collapse_user(self): # def test_collapse_user(self):
test_filename = "collapse-user_" + self.wikiq_out_name # test_filename = "collapse-user_" + self.wikiq_out_name
test_file = os.path.join(self.test_output_dir, test_filename) # test_file = os.path.join(self.test_output_dir, test_filename)
if os.path.exists(test_file): # if os.path.exists(test_file):
os.remove(test_file) # os.remove(test_file)
call = self.base_call.format(self.input_file, self.test_output_dir) # call = self.base_call.format(self.input_file, self.test_output_dir)
call = call + " --collapse-user" # call = call + " --collapse-user"
proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True) # proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
proc.wait() # proc.wait()
copyfile(self.call_output, test_file) # copyfile(self.call_output, test_file)
baseline_file = os.path.join(".", self.baseline_output_dir, test_filename) # baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
test = pd.read_table(test_file) # test = pd.read_table(test_file)
baseline = pd.read_table(baseline_file) # baseline = pd.read_table(baseline_file)
assert_frame_equal(test,baseline) # assert_frame_equal(test,baseline)
def test_pwr_segment(self): # def test_pwr_segment(self):
test_filename = "persistence_segment_" + self.wikiq_out_name # test_filename = "persistence_segment_" + self.wikiq_out_name
test_file = os.path.join(self.test_output_dir, test_filename) # test_file = os.path.join(self.test_output_dir, test_filename)
if os.path.exists(test_file): # if os.path.exists(test_file):
os.remove(test_file) # os.remove(test_file)
call = self.base_call.format(self.input_file, self.test_output_dir) # call = self.base_call.format(self.input_file, self.test_output_dir)
call = call + " --persistence segment" # call = call + " --persistence segment"
proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True) # print(call)
proc.wait() # proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
# proc.wait()
copyfile(self.call_output, test_file) # copyfile(self.call_output, test_file)
baseline_file = os.path.join(".", self.baseline_output_dir, test_filename) # baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
test = pd.read_table(test_file) # test = pd.read_table(test_file)
baseline = pd.read_table(baseline_file) # print(test)
assert_frame_equal(test,baseline) # baseline = pd.read_table(baseline_file)
# assert_frame_equal(test,baseline)
def test_pwr_legacy(self): # def test_pwr_legacy(self):
test_filename = "persistence_legacy_" + self.wikiq_out_name # test_filename = "persistence_legacy_" + self.wikiq_out_name
test_file = os.path.join(self.test_output_dir, test_filename) # test_file = os.path.join(self.test_output_dir, test_filename)
if os.path.exists(test_file): # if os.path.exists(test_file):
os.remove(test_file) # os.remove(test_file)
call = self.base_call.format(self.input_file, self.test_output_dir) # call = self.base_call.format(self.input_file, self.test_output_dir)
call = call + " --persistence legacy" # call = call + " --persistence legacy"
proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True) # proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
proc.wait() # proc.wait()
# copyfile(self.call_output, test_file)
# baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
# test = pd.read_table(test_file)
# baseline = pd.read_table(baseline_file)
# assert_frame_equal(test,baseline)
# def test_pwr(self):
# test_filename = "persistence_" + self.wikiq_out_name
# test_file = os.path.join(self.test_output_dir, test_filename)
# if os.path.exists(test_file):
# os.remove(test_file)
# call = self.base_call.format(self.input_file, self.test_output_dir)
# call = call + " --persistence"
# proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
# proc.wait()
copyfile(self.call_output, test_file) # copyfile(self.call_output, test_file)
baseline_file = os.path.join(".", self.baseline_output_dir, test_filename) # baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
test = pd.read_table(test_file) # test = pd.read_table(test_file)
baseline = pd.read_table(baseline_file) # baseline = pd.read_table(baseline_file)
assert_frame_equal(test,baseline) # assert_frame_equal(test,baseline)
def test_pwr(self):
test_filename = "persistence_" + self.wikiq_out_name
test_file = os.path.join(self.test_output_dir, test_filename)
if os.path.exists(test_file):
os.remove(test_file)
call = self.base_call.format(self.input_file, self.test_output_dir)
call = call + " --persistence"
proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
proc.wait()
copyfile(self.call_output, test_file) # def test_url_encode(self):
# test_filename = "url-encode_" + self.wikiq_out_name
baseline_file = os.path.join(".", self.baseline_output_dir, test_filename) # test_file = os.path.join(self.test_output_dir, test_filename)
# if os.path.exists(test_file):
# os.remove(test_file)
test = pd.read_table(test_file) # call = self.base_call.format(self.input_file, self.test_output_dir)
baseline = pd.read_table(baseline_file) # call = call + " --url-encode"
assert_frame_equal(test,baseline) # proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True)
# proc.wait()
# copyfile(self.call_output, test_file)
# baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
# test = pd.read_table(test_file)
# baseline = pd.read_table(baseline_file)
# assert_frame_equal(test,baseline)
def test_url_encode(self): # class Test_Malformed(unittest.TestCase):
test_filename = "url-encode_" + self.wikiq_out_name # def setUp(self):
# if not os.path.exists("test_output"):
# os.mkdir("test_output")
test_file = os.path.join(self.test_output_dir, test_filename) # self.wiki = 'twinpeaks'
if os.path.exists(test_file): # self.wikiq_out_name = self.wiki + ".tsv"
os.remove(test_file) # self.test_output_dir = os.path.join(".", "test_output")
# self.call_output = os.path.join(self.test_output_dir, self.wikiq_out_name)
call = self.base_call.format(self.input_file, self.test_output_dir) # self.infile = "{0}.xml.7z".format(self.wiki)
call = call + " --url-encode" # self.base_call = "../wikiq {0} -o {1}"
proc = subprocess.Popen(call,stdout=subprocess.PIPE,shell=True) # self.input_dir = "dumps"
proc.wait() # self.input_file = os.path.join(".", self.input_dir,self.infile)
copyfile(self.call_output, test_file)
baseline_file = os.path.join(".", self.baseline_output_dir, test_filename)
test = pd.read_table(test_file)
baseline = pd.read_table(baseline_file)
assert_frame_equal(test,baseline)
class Test_Malformed(unittest.TestCase): # def test_malformed_noargs(self):
def setUp(self):
if not os.path.exists("test_output"):
os.mkdir("test_output")
self.wiki = 'twinpeaks' # call = self.base_call.format(self.input_file, self.test_output_dir)
self.wikiq_out_name = self.wiki + ".tsv" # proc = subprocess.Popen(call,stdout=subprocess.PIPE,stderr=subprocess.PIPE, shell=True)
self.test_output_dir = os.path.join(".", "test_output") # proc.wait()
self.call_output = os.path.join(self.test_output_dir, self.wikiq_out_name) # outs, errs = proc.communicate()
# errlines = str(errs).split("\\n")
# self.assertEqual(errlines[-2],'xml.etree.ElementTree.ParseError: no element found: line 1369, column 0')
self.infile = "{0}.xml.7z".format(self.wiki) # class Test_Stdout(unittest.TestCase):
self.base_call = "../wikiq {0} -o {1}"
self.input_dir = "dumps"
self.input_file = os.path.join(".", self.input_dir,self.infile)
# def setUp(self):
# self.wiki = 'sailormoon'
# self.wikiq_out_name = self.wiki + ".tsv"
def test_malformed_noargs(self): # self.infile = "{0}.xml.7z".format(self.wiki)
# self.base_call = "../wikiq {0} --stdout"
# self.input_dir = "dumps"
# self.input_file = os.path.join(".", self.input_dir,self.infile)
# self.baseline_output_dir = "baseline_output"
call = self.base_call.format(self.input_file, self.test_output_dir) # def test_noargs(self):
proc = subprocess.Popen(call,stdout=subprocess.PIPE,stderr=subprocess.PIPE, shell=True)
proc.wait()
outs, errs = proc.communicate()
errlines = str(errs).split("\\n")
self.assertEqual(errlines[-2],'xml.etree.ElementTree.ParseError: no element found: line 1369, column 0')
class Test_Stdout(unittest.TestCase): # call = self.base_call.format(self.input_file)
# proc = subprocess.run(call,stdout=subprocess.PIPE,shell=True)
# outs = proc.stdout.decode("utf8")
def setUp(self): # test_file = "noargs_" + self.wikiq_out_name
self.wiki = 'sailormoon' # baseline_file = os.path.join(".", self.baseline_output_dir, test_file)
self.wikiq_out_name = self.wiki + ".tsv" # print(baseline_file)
# test = pd.read_table(StringIO(outs))
self.infile = "{0}.xml.7z".format(self.wiki) # baseline = pd.read_table(baseline_file)
self.base_call = "../wikiq {0} --stdout" # assert_frame_equal(test,baseline)
self.input_dir = "dumps"
self.input_file = os.path.join(".", self.input_dir,self.infile)
self.baseline_output_dir = "baseline_output"
def test_noargs(self):
call = self.base_call.format(self.input_file)
proc = subprocess.run(call,stdout=subprocess.PIPE,shell=True)
outs = proc.stdout.decode("utf8")
test_file = "noargs_" + self.wikiq_out_name
baseline_file = os.path.join(".", self.baseline_output_dir, test_file)
print(baseline_file)
test = pd.read_table(StringIO(outs))
baseline = pd.read_table(baseline_file)
assert_frame_equal(test,baseline)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

333
wikiq
View File

@ -1,9 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# original wikiq headers are: title articleid revid date_time anon # original wikiq headers are: title articleid revid date_time anon
# editor editor_id minor text_size text_entropy text_md5 reversion # editor editor_id minor text_size text_entropy text_md5 reversion
# additions_size deletions_size # additions_size deletions_size
import pdb
import argparse import argparse
import sys import sys
import os, os.path import os, os.path
@ -13,16 +13,63 @@ from subprocess import Popen, PIPE
from collections import deque from collections import deque
from hashlib import sha1 from hashlib import sha1
from mwxml import Dump from mwxml import Dump, Page
from deltas.tokenizers import wikitext_split from deltas.tokenizers import wikitext_split
from mwdiffs.utilities import dump2diffs
import mwpersistence import mwpersistence
from mwpersistence.state import Version, apply_opdocs, apply_operations, persist_revision_once
from mwpersistence import Token
from mwpersistence.utilities import diffs2persistence
import mwreverts import mwreverts
from urllib.parse import quote from urllib.parse import quote
TO_ENCODE = ('title', 'editor')
PERSISTENCE_RADIUS=7
from deltas import SequenceMatcher from deltas import SequenceMatcher
from deltas import SegmentMatcher from deltas import SegmentMatcher
TO_ENCODE = ('title', 'editor')
PERSISTENCE_RADIUS=7
# this is a simple override of mwpersistence.DiffState that doesn't do anything special for reverts.
class WikiqDiffState(mwpersistence.DiffState):
def _update(self, text=None, checksum=None, opdocs=None, revision=None):
if checksum is None:
if text is None:
raise TypeError("Either 'text' or 'checksum' must be " +
"specified.")
else:
checksum = sha1(bytes(text, 'utf8')).hexdigest()
current_version = Version()
# the main difference we have is that we don't do anything special for reverts
if opdocs is not None:
transition = apply_opdocs(opdocs, self.last.tokens or [])
current_version.tokens, _, _ = transition
else:
# NOTICE: HEAVY COMPUTATION HERE!!!
#
# Diffs usually run in O(n^2) -- O(n^3) time and most
# tokenizers produce a lot of tokens.
if self.diff_processor is None:
raise RuntimeError("DiffState cannot process raw text " +
"without a diff_engine specified.")
operations, _, current_tokens = \
self.diff_processor.process(text, token_class=Token)
transition = apply_operations(operations,
self.last.tokens or [],
current_tokens)
current_version.tokens, _, _ = transition
# Record persistence
persist_revision_once(current_version.tokens, revision)
# Update last version
self.last = current_version
# Return the tranisitoned state
return transition
class PersistMethod: class PersistMethod:
none = 0 none = 0
@ -34,97 +81,150 @@ def calculate_persistence(tokens_added):
return(sum([(len(x.revisions)-1) for x in tokens_added]), return(sum([(len(x.revisions)-1) for x in tokens_added]),
len(tokens_added)) len(tokens_added))
class WikiqIterator(): class WikiqIterator(Dump):
def __init__(self, fh, collapse_user=False):
self.fh = fh
self.collapse_user = collapse_user
self.mwiterator = Dump.from_file(self.fh)
self.namespace_map = { ns.id : ns.name for ns in
self.mwiterator.site_info.namespaces }
self.__pages = self.load_pages()
def load_pages(self): @classmethod
for page in self.mwiterator: def from_file(cls, fh, collapse_user = False):
yield WikiqPage(page, cls = super(WikiqIterator, cls).from_file(fh)
namespace_map = self.namespace_map, cls.fh = fh
collapse_user=self.collapse_user) cls.collapse_user = collapse_user
cls.namespace_map = { ns.id : ns.name for ns in
cls.site_info.namespaces }
return cls
def __iter__(self): @classmethod
return self.__pages def process_item(cls, item_element, namespace_map, collapse_user = False):
if item_element.tag == "page":
return WikiqPage.from_element(item_element, namespace_map, collapse_user)
elif item_element.tag == "logitem":
return LogItem.from_element(item_element, namespace_map)
else:
raise MalformedXML("Expected to see <page> or <logitem>. " +
"Instead saw <{0}>".format(item_element.tag))
def __next__(self): class WikiqPage(Page):
return next(self._pages)
class WikiqPage():
__slots__ = ('id', 'title', 'namespace', 'redirect', __slots__ = ('id', 'title', 'namespace', 'redirect',
'restrictions', 'mwpage', '__revisions', 'restrictions','collapse_user')
'collapse_user')
@classmethod
def from_element(cls, item_element, namespace_map, collapse_user = False):
cls.prev_rev = None
inv_namespace_map = {ns.id:name for name,ns in namespace_map.items()}
cls = super(WikiqPage, cls).from_element(item_element, namespace_map)
def __init__(self, page, namespace_map, collapse_user=False):
self.id = page.id
self.namespace = page.namespace
# following mwxml, we assume namespace 0 in cases where # following mwxml, we assume namespace 0 in cases where
# page.namespace is inconsistent with namespace_map # page.namespace is inconsistent with namespace_map
if page.namespace not in namespace_map: # this undoes the "correction" of the namespace in mwxml
self.title = page.title
page.namespace = 0 if cls.namespace not in inv_namespace_map:
if page.namespace != 0: cls.namespace = 0
self.title = ':'.join([namespace_map[page.namespace], page.title]) if cls.namespace != 0:
cls.title = ':'.join([inv_namespace_map[cls.namespace], cls.title])
cls.collapse_user = collapse_user
cls.revisions = cls._Page__revisions
return cls
@staticmethod
def _correct_sha(rev_data):
if rev_data.deleted.text:
rev_data.text = ""
rev_data.text_chars = 0
rev_data.sha1 = ""
rev_data.revert = ""
rev_data.reverteds = ""
else: else:
self.title = page.title if rev_data.text is None :
self.restrictions = page.restrictions rev_data.text = ""
self.collapse_user = collapse_user
self.mwpage = page
self.__revisions = self.rev_list()
def rev_list(self): rev_data.text_chars = len(rev_data.text)
# Outline for how we want to handle collapse_user=True
# iteration rev.user prev_rev.user add prev_rev?
# 0 A None Never
# 1 A A False
# 2 B A True
# 3 A B True
# 4 A A False
# Post-loop A Always
for i, rev in enumerate(self.mwpage):
# never yield the first time
if i == 0:
if self.collapse_user:
collapsed_revs = 1
rev.collapsed_revs = collapsed_revs
else: if hasattr(rev_data,"sha1") and rev_data.sha1 is not None:
if self.collapse_user: text_sha1 = rev_data.sha1
# yield if this is the last edit in a seq by a user and reset
# also yield if we do know who the user is
if rev.deleted.user or prev_rev.deleted.user: else:
yield prev_rev text_sha1 = sha1(bytes(rev_data.text, "utf8")).hexdigest()
collapsed_revs = 1
rev.collapsed_revs = collapsed_revs
elif not rev.user.text == prev_rev.user.text: rev_data.sha1 = text_sha1
yield prev_rev
collapsed_revs = 1 return rev_data
rev.collapsed_revs = collapsed_revs
# otherwise, add one to the counter # Outline for how we want to handle collapse_user=True
else: # iteration rev.user prev_rev.user add prev_rev?
collapsed_revs += 1 # 0 A None Never
rev.collapsed_revs = collapsed_revs # 1 A A False
# if collapse_user is false, we always yield # 2 B A True
# 3 A B True
# 4 A A False
# Post-loop A Always
def __find_next_revision(self):
if self.prev_rev is None:
prev_rev = WikiqPage._correct_sha(next(self.revisions))
self.prev_rev = prev_rev
else:
prev_rev = self.prev_rev
if self.collapse_user:
collapsed_revs = 1
rev.collapsed_revs = collapsed_revs
for rev in self.revisions:
rev = WikiqPage._correct_sha(rev)
if self.collapse_user:
# yield if this is the last edit in a seq by a user and reset
# also yield if we do know who the user is
if rev.deleted.user or prev_rev.deleted.user:
self.prev_rev = rev
if prev_rev is not None:
prev_rev.collapsed_revs = collapsed_revs
return prev_rev
elif not rev.user.text == prev_rev.user.text:
self.prev_rev = rev
if prev_rev is not None:
prev_rev.collapsed_revs = collapsed_revs
return prev_rev
# otherwise, add one to the counter
else: else:
yield prev_rev collapsed_revs += 1
rev.collapsed_revs = collapsed_revs
# if collapse_user is false, we always yield
else:
self.prev_rev = rev
if prev_rev is not None:
return prev_rev
prev_rev = rev prev_rev = rev
# also yield the final time self.prev_rev = None
yield prev_rev
if self.collapse_user:
prev_rev.collapsed_revs = collapsed_revs
return prev_rev
def __iter__(self):
return self.__revisions
def __next__(self): def __next__(self):
return next(self.__revisions) revision = self.__find_next_revision()
revision.page = self
return revision
def __iter__(self):
while(True):
revision = self.__find_next_revision()
revision.page = self
yield revision
# def __iter__(self):
# return self.__revisions
# def __next__(self):
# return next(self.__revisions)
class WikiqParser(): class WikiqParser():
@ -133,7 +233,6 @@ class WikiqParser():
Parameters: Parameters:
persist : what persistence method to use. Takes a PersistMethod value persist : what persistence method to use. Takes a PersistMethod value
""" """
self.input_file = input_file self.input_file = input_file
self.output_file = output_file self.output_file = output_file
self.collapse_user = collapse_user self.collapse_user = collapse_user
@ -146,46 +245,54 @@ class WikiqParser():
else: else:
self.namespace_filter = None self.namespace_filter = None
def __get_namespace_from_title(self, title):
default_ns = None
for ns in self.namespaces:
# skip if the namespace is not defined
if ns == None:
default_ns = self.namespaces[ns]
continue
if title.startswith(ns + ":"):
return self.namespaces[ns]
# if we've made it this far with no matches, we return the default namespace
return default_ns
def process(self):
# create a regex that creates the output filename # create a regex that creates the output filename
# output_filename = re.sub(r'^.*/(enwiki\-\d+)\-.*p(\d+)p.*$', # output_filename = re.sub(r'^.*/(enwiki\-\d+)\-.*p(\d+)p.*$',
# r'output/wikiq-\1-\2.tsv', # r'output/wikiq-\1-\2.tsv',
# input_filename) # input_filename)
# Construct dump file iterator # Construct dump file iterator
dump = WikiqIterator(self.input_file, collapse_user=self.collapse_user) self.dump = WikiqIterator.from_file(self.input_file, self.collapse_user)
# extract list of namspaces self.diff_engine = None
self.namespaces = {ns.name : ns.id for ns in dump.mwiterator.site_info.namespaces}
if self.persist == PersistMethod.sequence:
self.diff_engine = SequenceMatcher(tokenizer = wikitext_split)
if self.persist == PersistMethod.segment:
self.diff_engine = SegmentMatcher(tokenizer = wikitext_split)
# def __get_namespace_from_title(self, title):
# default_ns = None
# for ns in self.namespaces:
# # skip if the namespace is not defined
# if ns == None:
# default_ns = self.namespaces[ns]
# continue
# if title.startswith(ns + ":"):
# return self.namespaces[ns]
# # if we've made it this far with no matches, we return the default namespace
# return default_ns
# def _set_namespace(self, rev_docs):
# for rev_data in rev_docs:
# if 'namespace' not in rev_data['page']:
# namespace = self.__get_namespace_from_title(page['title'])
# rev_data['page']['namespace'] = namespace
# yield rev_data
def process(self):
page_count = 0 page_count = 0
rev_count = 0 rev_count = 0
for page in self.dump:
# Iterate through pages # skip pages not in the namespaces we want
for page in dump: if self.namespace_filter is not None and page.namespace not in self.namespace_filter:
namespace = page.namespace if page.namespace is not None else self.__get_namespace_from_title(page.title) continue
# skip namespaces not in the filter
if self.namespace_filter is not None:
if namespace not in self.namespace_filter:
continue
rev_detector = mwreverts.Detector() rev_detector = mwreverts.Detector()
@ -193,27 +300,25 @@ class WikiqParser():
window = deque(maxlen=PERSISTENCE_RADIUS) window = deque(maxlen=PERSISTENCE_RADIUS)
if self.persist == PersistMethod.sequence: if self.persist == PersistMethod.sequence:
state = mwpersistence.DiffState(SequenceMatcher(tokenizer = wikitext_split), state = WikiqDiffState(SequenceMatcher(tokenizer = wikitext_split),
revert_radius=PERSISTENCE_RADIUS) revert_radius=PERSISTENCE_RADIUS)
elif self.persist == PersistMethod.segment: elif self.persist == PersistMethod.segment:
state = mwpersistence.DiffState(SegmentMatcher(tokenizer = wikitext_split), state = WikiqDiffState(SegmentMatcher(tokenizer = wikitext_split),
revert_radius=PERSISTENCE_RADIUS) revert_radius=PERSISTENCE_RADIUS)
# self.persist == PersistMethod.legacy
else: else:
from mw.lib import persistence from mw.lib import persistence
state = persistence.State() state = persistence.State()
# Iterate through a page's revisions # Iterate through a page's revisions
for rev in page: for rev in page:
rev_data = {'revid' : rev.id, rev_data = {'revid' : rev.id,
'date_time' : rev.timestamp.strftime('%Y-%m-%d %H:%M:%S'), 'date_time' : rev.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
'articleid' : page.id, 'articleid' : page.id,
'editor_id' : "" if rev.deleted.user == True or rev.user.id is None else rev.user.id, 'editor_id' : "" if rev.deleted.user == True or rev.user.id is None else rev.user.id,
'title' : '"' + page.title + '"', 'title' : '"' + page.title + '"',
'namespace' : namespace, 'namespace' : page.namespace,
'deleted' : "TRUE" if rev.deleted.text else "FALSE" } 'deleted' : "TRUE" if rev.deleted.text else "FALSE" }
# if revisions are deleted, /many/ things will be missing # if revisions are deleted, /many/ things will be missing
@ -270,8 +375,8 @@ class WikiqParser():
#TODO missing: additions_size deletions_size #TODO missing: additions_size deletions_size
# if collapse user was on, lets run that # if collapse user was on, lets run that
if self.collapse_user: # if self.collapse_user:
rev_data['collapsed_revs'] = rev.collapsed_revs # rev_data.collapsed_revs = rev.collapsed_revs
if self.persist != PersistMethod.none: if self.persist != PersistMethod.none:
if rev.deleted.text: if rev.deleted.text: