~ubuntu-branches/ubuntu/lucid/anki/lucid-updates

« back to all changes in this revision

Viewing changes to libanki/anki/importing/supermemo_xml.py

  • Committer: Bazaar Package Importer
  • Author(s): Mackenzie Morgan
  • Date: 2010-05-31 15:55:50 UTC
  • mfrom: (7.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20100531155550-wj3tag8bvp6fwhpo
Tags: 0.9.9.8.6-2~lucid1
Backport from maverick to fix FTBFS (LP: #550145)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
# Copyright: petr.michalec@gmail.com
 
3
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
 
4
 
 
5
"""\
 
6
Importing Supermemo XML decks
 
7
==============================
 
8
"""
 
9
__docformat__ = 'restructuredtext'
 
10
 
 
11
import sys
 
12
 
 
13
from anki.importing import Importer, ForeignCard
 
14
from anki.lang import _
 
15
from anki.errors import *
 
16
 
 
17
from xml.dom import minidom, Node
 
18
from types import DictType, InstanceType
 
19
from string import capwords, maketrans
 
20
import re, unicodedata, time
 
21
#import chardet
 
22
 
 
23
 
 
24
from anki.deck import Deck
 
25
 
 
26
class SmartDict(dict):
 
27
    """ 
 
28
    See http://www.peterbe.com/plog/SmartDict
 
29
    Copyright 2005, Peter Bengtsson, peter@fry-it.com
 
30
 
 
31
    A smart dict can be instanciated either from a pythonic dict 
 
32
    or an instance object (eg. SQL recordsets) but it ensures that you can 
 
33
    do all the convenient lookups such as x.first_name, x['first_name'] or
 
34
    x.get('first_name').
 
35
    """
 
36
    
 
37
    def __init__(self, *a, **kw):
 
38
        if a:
 
39
            if type(a[0]) is DictType:
 
40
                kw.update(a[0])
 
41
            elif type(a[0]) is InstanceType:
 
42
                kw.update(a[0].__dict__)
 
43
            elif hasattr(a[0], '__class__') and a[0].__class__.__name__=='SmartDict':
 
44
                kw.update(a[0].__dict__)
 
45
                
 
46
        dict.__init__(self, **kw)
 
47
        self.__dict__ = self
 
48
        
 
49
class SuperMemoElement(SmartDict):
 
50
  "SmartDict wrapper to store SM Element data"
 
51
 
 
52
  def __init__(self, *a, **kw):
 
53
    SmartDict.__init__(self, *a, **kw)
 
54
    #default content
 
55
    self.__dict__['lTitle'] = None
 
56
    self.__dict__['Title'] = None
 
57
    self.__dict__['Question'] = None
 
58
    self.__dict__['Answer'] = None
 
59
    self.__dict__['Count'] = None
 
60
    self.__dict__['Type'] = None
 
61
    self.__dict__['ID'] = None
 
62
    self.__dict__['Interval'] = None
 
63
    self.__dict__['Lapses'] = None
 
64
    self.__dict__['Repetitions'] = None
 
65
    self.__dict__['LastRepetiton'] = None
 
66
    self.__dict__['AFactor'] = None
 
67
    self.__dict__['UFactor'] = None
 
68
 
 
69
 
 
70
 
 
71
# This is an AnkiImporter
 
72
class SupermemoXmlImporter(Importer):
 
73
    """
 
74
    Supermemo XML export's to Anki parser.
 
75
    Goes through a SM collection and fetch all elements. 
 
76
 
 
77
    My SM collection was a big mess where topics and items were mixed.
 
78
    I was unable to parse my content in a regular way like for loop on
 
79
    minidom.getElementsByTagName() etc. My collection had also an
 
80
    limitation, topics were splited into branches with max 100 items
 
81
    on each. Learning themes were in deep structure. I wanted to have
 
82
    full title on each element to be stored in tags. 
 
83
 
 
84
    Code should be upgrade to support importing of SM2006 exports.
 
85
    """
 
86
 
 
87
    def __init__(self, *args):
 
88
        """Initialize internal varables.
 
89
        Pameters to be exposed to GUI are stored in self.META"""
 
90
 
 
91
        Importer.__init__(self, *args)
 
92
        self.lines = None
 
93
        self.numFields=int(2)
 
94
 
 
95
        # SmXmlParse VARIABLES
 
96
        self.xmldoc = None
 
97
        self.pieces = []
 
98
        self.cntBuf = [] #to store last parsed data
 
99
        self.cntElm = [] #to store SM Elements data
 
100
        self.cntCol = [] #to store SM Colections data
 
101
 
 
102
        # store some meta info related to parse algorithm
 
103
        # SmartDict works like dict / class wrapper
 
104
        self.cntMeta = SmartDict()
 
105
        self.cntMeta.popTitles = False
 
106
        self.cntMeta.title     = []
 
107
 
 
108
        # META stores controls of import scritp, should be
 
109
        # exposed to import dialog. These are default values.
 
110
        self.META = SmartDict()
 
111
        self.META.resetLearningData  = False            # implemented
 
112
        self.META.onlyMemorizedItems = False            # implemented
 
113
        self.META.loggerLevel = 2                       # implemented 0no,1info,2error,3debug
 
114
        self.META.tagAllTopics = True
 
115
        self.META.pathsToBeTagged = ['English for begginers', 'Advanced English 97', 'Phrasal Verbs']                # path patterns to be tagged - in gui entered like 'Advanced English 97|My Vocablary'
 
116
        self.META.tagMemorizedItems = True              # implemented
 
117
        self.META.logToStdOutput   = False              # implemented
 
118
 
 
119
        self.cards = []
 
120
 
 
121
## TOOLS
 
122
 
 
123
    def _fudgeText(self, text):
 
124
        "Replace sm syntax to Anki syntax"
 
125
        text = text.replace("\n\r", u"<br>")
 
126
        text = text.replace("\n", u"<br>")
 
127
        return text
 
128
 
 
129
    def _unicode2ascii(self,str):
 
130
        "Remove diacritic punctuation from strings (titles)"
 
131
        return u"".join([ c for c in unicodedata.normalize('NFKD', str) if not unicodedata.combining(c)])
 
132
 
 
133
    def _decode_htmlescapes(self,s):
 
134
        """Unescape HTML code."""
 
135
        #In case of bad formated html you can import MinimalSoup etc.. see btflsoup source code
 
136
        from BeautifulSoup import BeautifulStoneSoup as btflsoup    
 
137
 
 
138
        #my sm2004 also ecaped & char in escaped sequences.
 
139
        s = re.sub(u'&amp;',u'&',s) 
 
140
        #unescaped solitary chars < or > that were ok for minidom confuse btfl soup
 
141
        s = re.sub(u'>',u'&gt;',s) 
 
142
        s = re.sub(u'<',u'&lt;',s) 
 
143
 
 
144
        return unicode(btflsoup(s,convertEntities=btflsoup.HTML_ENTITIES ))
 
145
 
 
146
 
 
147
    def _unescape(self,s,initilize):
 
148
        """Note: This method is not used, BeautifulSoup does better job.
 
149
        """
 
150
 
 
151
        if self._unescape_trtable == None: 
 
152
            self._unescape_trtable = (
 
153
              ('&euro;',u'€'), ('&#32;',u' '), ('&#33;',u'!'), ('&#34;',u'"'), ('&#35;',u'#'), ('&#36;',u'$'), ('&#37;',u'%'), ('&#38;',u'&'), ('&#39;',u"'"),
 
154
              ('&#40;',u'('), ('&#41;',u')'), ('&#42;',u'*'), ('&#43;',u'+'), ('&#44;',u','), ('&#45;',u'-'), ('&#46;',u'.'), ('&#47;',u'/'), ('&#48;',u'0'),
 
155
              ('&#49;',u'1'), ('&#50;',u'2'), ('&#51;',u'3'), ('&#52;',u'4'), ('&#53;',u'5'), ('&#54;',u'6'), ('&#55;',u'7'), ('&#56;',u'8'), ('&#57;',u'9'),
 
156
              ('&#58;',u':'), ('&#59;',u';'), ('&#60;',u'<'), ('&#61;',u'='), ('&#62;',u'>'), ('&#63;',u'?'), ('&#64;',u'@'), ('&#65;',u'A'), ('&#66;',u'B'),
 
157
              ('&#67;',u'C'), ('&#68;',u'D'), ('&#69;',u'E'), ('&#70;',u'F'), ('&#71;',u'G'), ('&#72;',u'H'), ('&#73;',u'I'), ('&#74;',u'J'), ('&#75;',u'K'),
 
158
              ('&#76;',u'L'), ('&#77;',u'M'), ('&#78;',u'N'), ('&#79;',u'O'), ('&#80;',u'P'), ('&#81;',u'Q'), ('&#82;',u'R'), ('&#83;',u'S'), ('&#84;',u'T'),
 
159
              ('&#85;',u'U'), ('&#86;',u'V'), ('&#87;',u'W'), ('&#88;',u'X'), ('&#89;',u'Y'), ('&#90;',u'Z'), ('&#91;',u'['), ('&#92;',u'\\'), ('&#93;',u']'),
 
160
              ('&#94;',u'^'), ('&#95;',u'_'), ('&#96;',u'`'), ('&#97;',u'a'), ('&#98;',u'b'), ('&#99;',u'c'), ('&#100;',u'd'), ('&#101;',u'e'), ('&#102;',u'f'),
 
161
              ('&#103;',u'g'), ('&#104;',u'h'), ('&#105;',u'i'), ('&#106;',u'j'), ('&#107;',u'k'), ('&#108;',u'l'), ('&#109;',u'm'), ('&#110;',u'n'),
 
162
              ('&#111;',u'o'), ('&#112;',u'p'), ('&#113;',u'q'), ('&#114;',u'r'), ('&#115;',u's'), ('&#116;',u't'), ('&#117;',u'u'), ('&#118;',u'v'),
 
163
              ('&#119;',u'w'), ('&#120;',u'x'), ('&#121;',u'y'), ('&#122;',u'z'), ('&#123;',u'{'), ('&#124;',u'|'), ('&#125;',u'}'), ('&#126;',u'~'),
 
164
              ('&#160;',u' '), ('&#161;',u'¡'), ('&#162;',u'¢'), ('&#163;',u'£'), ('&#164;',u'¤'), ('&#165;',u'¥'), ('&#166;',u'¦'), ('&#167;',u'§'),
 
165
              ('&#168;',u'¨'), ('&#169;',u'©'), ('&#170;',u'ª'), ('&#171;',u'«'), ('&#172;',u'¬'), ('&#173;',u'­'), ('&#174;',u'®'), ('&#175;',u'¯'),
 
166
              ('&#176;',u'°'), ('&#177;',u'±'), ('&#178;',u'²'), ('&#179;',u'³'), ('&#180;',u'´'), ('&#181;',u'µ'), ('&#182;',u'¶'), ('&#183;',u'·'),
 
167
              ('&#184;',u'¸'), ('&#185;',u'¹'), ('&#186;',u'º'), ('&#187;',u'»'), ('&#188;',u'¼'), ('&#189;',u'½'), ('&#190;',u'¾'), ('&#191;',u'¿'),
 
168
              ('&#192;',u'À'), ('&#193;',u'Á'), ('&#194;',u'Â'), ('&#195;',u'Ã'), ('&#196;',u'Ä'), ('&Aring;',u'Å'), ('&#197;',u'Å'), ('&#198;',u'Æ'),
 
169
              ('&#199;',u'Ç'), ('&#200;',u'È'), ('&#201;',u'É'), ('&#202;',u'Ê'), ('&#203;',u'Ë'), ('&#204;',u'Ì'), ('&#205;',u'Í'), ('&#206;',u'Î'),
 
170
              ('&#207;',u'Ï'), ('&#208;',u'Ð'), ('&#209;',u'Ñ'), ('&#210;',u'Ò'), ('&#211;',u'Ó'), ('&#212;',u'Ô'), ('&#213;',u'Õ'), ('&#214;',u'Ö'),
 
171
              ('&#215;',u'×'), ('&#216;',u'Ø'), ('&#217;',u'Ù'), ('&#218;',u'Ú'), ('&#219;',u'Û'), ('&#220;',u'Ü'), ('&#221;',u'Ý'), ('&#222;',u'Þ'),
 
172
              ('&#223;',u'ß'), ('&#224;',u'à'), ('&#225;',u'á'), ('&#226;',u'â'), ('&#227;',u'ã'), ('&#228;',u'ä'), ('&#229;',u'å'), ('&#230;',u'æ'),
 
173
              ('&#231;',u'ç'), ('&#232;',u'è'), ('&#233;',u'é'), ('&#234;',u'ê'), ('&#235;',u'ë'), ('&#236;',u'ì'), ('&iacute;',u'í'), ('&#237;',u'í'),
 
174
              ('&#238;',u'î'), ('&#239;',u'ï'), ('&#240;',u'ð'), ('&#241;',u'ñ'), ('&#242;',u'ò'), ('&#243;',u'ó'), ('&#244;',u'ô'), ('&#245;',u'õ'),
 
175
              ('&#246;',u'ö'), ('&#247;',u'÷'), ('&#248;',u'ø'), ('&#249;',u'ù'), ('&#250;',u'ú'), ('&#251;',u'û'), ('&#252;',u'ü'), ('&#253;',u'ý'),
 
176
              ('&#254;',u'þ'), ('&#255;',u'ÿ'), ('&#256;',u'Ā'), ('&#257;',u'ā'), ('&#258;',u'Ă'), ('&#259;',u'ă'), ('&#260;',u'Ą'), ('&#261;',u'ą'),
 
177
              ('&#262;',u'Ć'), ('&#263;',u'ć'), ('&#264;',u'Ĉ'), ('&#265;',u'ĉ'), ('&#266;',u'Ċ'), ('&#267;',u'ċ'), ('&#268;',u'Č'), ('&#269;',u'č'),
 
178
              ('&#270;',u'Ď'), ('&#271;',u'ď'), ('&#272;',u'Đ'), ('&#273;',u'đ'), ('&#274;',u'Ē'), ('&#275;',u'ē'), ('&#276;',u'Ĕ'), ('&#277;',u'ĕ'),
 
179
              ('&#278;',u'Ė'), ('&#279;',u'ė'), ('&#280;',u'Ę'), ('&#281;',u'ę'), ('&#282;',u'Ě'), ('&#283;',u'ě'), ('&#284;',u'Ĝ'), ('&#285;',u'ĝ'),
 
180
              ('&#286;',u'Ğ'), ('&#287;',u'ğ'), ('&#288;',u'Ġ'), ('&#289;',u'ġ'), ('&#290;',u'Ģ'), ('&#291;',u'ģ'), ('&#292;',u'Ĥ'), ('&#293;',u'ĥ'),
 
181
              ('&#294;',u'Ħ'), ('&#295;',u'ħ'), ('&#296;',u'Ĩ'), ('&#297;',u'ĩ'), ('&#298;',u'Ī'), ('&#299;',u'ī'), ('&#300;',u'Ĭ'), ('&#301;',u'ĭ'),
 
182
              ('&#302;',u'Į'), ('&#303;',u'į'), ('&#304;',u'İ'), ('&#305;',u'ı'), ('&#306;',u'IJ'), ('&#307;',u'ij'), ('&#308;',u'Ĵ'), ('&#309;',u'ĵ'),
 
183
              ('&#310;',u'Ķ'), ('&#311;',u'ķ'), ('&#312;',u'ĸ'), ('&#313;',u'Ĺ'), ('&#314;',u'ĺ'), ('&#315;',u'Ļ'), ('&#316;',u'ļ'), ('&#317;',u'Ľ'),
 
184
              ('&#318;',u'ľ'), ('&#319;',u'Ŀ'), ('&#320;',u'ŀ'), ('&#321;',u'Ł'), ('&#322;',u'ł'), ('&#323;',u'Ń'), ('&#324;',u'ń'), ('&#325;',u'Ņ'),
 
185
              ('&#326;',u'ņ'), ('&#327;',u'Ň'), ('&#328;',u'ň'), ('&#329;',u'ʼn'), ('&#330;',u'Ŋ'), ('&#331;',u'ŋ'), ('&#332;',u'Ō'), ('&#333;',u'ō'),
 
186
              ('&#334;',u'Ŏ'), ('&#335;',u'ŏ'), ('&#336;',u'Ő'), ('&#337;',u'ő'), ('&#338;',u'Œ'), ('&#339;',u'œ'), ('&#340;',u'Ŕ'), ('&#341;',u'ŕ'),
 
187
              ('&#342;',u'Ŗ'), ('&#343;',u'ŗ'), ('&#344;',u'Ř'), ('&#345;',u'ř'), ('&#346;',u'Ś'), ('&#347;',u'ś'), ('&#348;',u'Ŝ'), ('&#349;',u'ŝ'),
 
188
              ('&#350;',u'Ş'), ('&#351;',u'ş'), ('&#352;',u'Š'), ('&#353;',u'š'), ('&#354;',u'Ţ'), ('&#355;',u'ţ'), ('&#356;',u'Ť'), ('&#357;',u'ť'),
 
189
              ('&#358;',u'Ŧ'), ('&#359;',u'ŧ'), ('&#360;',u'Ũ'), ('&#361;',u'ũ'), ('&#362;',u'Ū'), ('&#363;',u'ū'), ('&#364;',u'Ŭ'), ('&#365;',u'ŭ'),
 
190
              ('&#366;',u'Ů'), ('&#367;',u'ů'), ('&#368;',u'Ű'), ('&#369;',u'ű'), ('&#370;',u'Ų'), ('&#371;',u'ų'), ('&#372;',u'Ŵ'), ('&#373;',u'ŵ'),
 
191
              ('&#374;',u'Ŷ'), ('&#375;',u'ŷ'), ('&#376;',u'Ÿ'), ('&#377;',u'Ź'), ('&#378;',u'ź'), ('&#379;',u'Ż'), ('&#380;',u'ż'), ('&#381;',u'Ž'),
 
192
              ('&#382;',u'ž'), ('&#383;',u'ſ'), ('&#340;',u'Ŕ'), ('&#341;',u'ŕ'), ('&#342;',u'Ŗ'), ('&#343;',u'ŗ'), ('&#344;',u'Ř'), ('&#345;',u'ř'),
 
193
              ('&#346;',u'Ś'), ('&#347;',u'ś'), ('&#348;',u'Ŝ'), ('&#349;',u'ŝ'), ('&#350;',u'Ş'), ('&#351;',u'ş'), ('&#352;',u'Š'), ('&#353;',u'š'),
 
194
              ('&#354;',u'Ţ'), ('&#355;',u'ţ'), ('&#356;',u'Ť'), ('&#577;',u'ť'), ('&#358;',u'Ŧ'), ('&#359;',u'ŧ'), ('&#360;',u'Ũ'), ('&#361;',u'ũ'),
 
195
              ('&#362;',u'Ū'), ('&#363;',u'ū'), ('&#364;',u'Ŭ'), ('&#365;',u'ŭ'), ('&#366;',u'Ů'), ('&#367;',u'ů'), ('&#368;',u'Ű'), ('&#369;',u'ű'),
 
196
              ('&#370;',u'Ų'), ('&#371;',u'ų'), ('&#372;',u'Ŵ'), ('&#373;',u'ŵ'), ('&#374;',u'Ŷ'), ('&#375;',u'ŷ'), ('&#376;',u'Ÿ'), ('&#377;',u'Ź'),
 
197
              ('&#378;',u'ź'), ('&#379;',u'Ż'), ('&#380;',u'ż'), ('&#381;',u'Ž'), ('&#382;',u'ž'), ('&#383;',u'ſ'),
 
198
          ) 
 
199
 
 
200
 
 
201
      #m = re.match()
 
202
      #s = s.replace(code[0], code[1])
 
203
 
 
204
## DEFAULT IMPORTER METHODS
 
205
 
 
206
    def foreignCards(self):
 
207
 
 
208
        # Load file and parse it by minidom
 
209
        self.loadSource(self.file)
 
210
 
 
211
        # Migrating content / time consuming part
 
212
        # addItemToCards is called for each sm element
 
213
        self.logger(u'Parsing started.')
 
214
        self.parse()
 
215
        self.logger(u'Parsing done.')
 
216
        
 
217
        # Return imported cards
 
218
        return self.cards
 
219
 
 
220
    def fields(self):
 
221
        return 2
 
222
 
 
223
## PARSER METHODS
 
224
 
 
225
    def addItemToCards(self,item):
 
226
        "This method actually do conversion"
 
227
 
 
228
        # new anki card
 
229
        card = ForeignCard()
 
230
 
 
231
        # clean Q and A
 
232
        card.fields.append(self._fudgeText(self._decode_htmlescapes(item.Question)))
 
233
        card.fields.append(self._fudgeText(self._decode_htmlescapes(item.Answer)))
 
234
        card.tags = u""
 
235
 
 
236
        # pre-process scheduling data
 
237
        tLastrep = time.mktime(time.strptime(item.LastRepetition, '%d.%m.%Y'))
 
238
        tToday = time.time()
 
239
        
 
240
        # convert learning data 
 
241
        if not self.META.resetLearningData:
 
242
            # migration of LearningData algorithm
 
243
            card.interval = item.Interval
 
244
            card.successive = item.Repetitions
 
245
            ##card.due = tToday + (float(item.Interval) * 86400.0) - tLastrep
 
246
            card.due = tLastrep + (float(item.Interval) * 86400.0)
 
247
            card.lastDue = 0
 
248
 
 
249
            card.factor = float(item.AFactor.replace(',','.'))
 
250
            card.lastFactor = float(item.AFactor.replace(',','.'))
 
251
 
 
252
            # SM is not exporting all the information Anki keeps track off, so it
 
253
            # needs to be fudged
 
254
            card.youngEase0 = item.Lapses
 
255
            card.youngEase3 = item.Repetitions + item.Lapses
 
256
            card.yesCount = item.Repetitions
 
257
            card.noCount  = item.Lapses
 
258
            card.reps = card.yesCount + card.noCount
 
259
            card.spaceUntil = card.due
 
260
            card.combinedDue = card.due
 
261
 
 
262
        # categories & tags
 
263
        # it's worth to have every theme (tree structure of sm collection) stored in tags, but sometimes not
 
264
        # you can deceide if you are going to tag all toppics or just that containing some pattern
 
265
        tTaggTitle = False
 
266
        for pattern in self.META.pathsToBeTagged:
 
267
            if item.lTitle != None and pattern.lower() in u" ".join(item.lTitle).lower():
 
268
              tTaggTitle = True
 
269
              break
 
270
        if tTaggTitle or self.META.tagAllTopics:
 
271
          # normalize - remove diacritic punctuation from unicode chars to ascii
 
272
          item.lTitle = [ self._unicode2ascii(topic) for topic in item.lTitle]
 
273
          
 
274
          # Transfrom xyz / aaa / bbb / ccc on Title path to Tag  xyzAaaBbbCcc
 
275
          #  clean things like [999] or [111-2222] from title path, example: xyz / [1000-1200] zyx / xyz
 
276
          #  clean whitespaces
 
277
          #  set Capital letters for first char of the word
 
278
          tmp = list(set([ re.sub('(\[[0-9]+\])'   , ' ' , i ).replace('_',' ')  for i in item.lTitle ]))
 
279
          tmp = list(set([ re.sub('(\W)',' ', i )  for i in tmp ]))
 
280
          tmp = list(set([ re.sub( '^[0-9 ]+$','',i)  for i in tmp ]))
 
281
          tmp = list(set([ capwords(i).replace(' ','')  for i in tmp ]))
 
282
          tags = [ j[0].lower() + j[1:] for j in tmp if j.strip() <> '']
 
283
 
 
284
          card.tags += u" ".join(tags)
 
285
 
 
286
          if self.META.tagMemorizedItems and item.Interval >0:
 
287
            card.tags += " Memorized"
 
288
 
 
289
          self.logger(u'Element tags\t- ' + card.tags, level=3)
 
290
 
 
291
        self.cards.append(card)
 
292
 
 
293
    def logger(self,text,level=1):
 
294
        "Wrapper for Anki logger"
 
295
 
 
296
        dLevels={0:'',1:u'Info',2:u'Verbose',3:u'Debug'}
 
297
        if level<=self.META.loggerLevel:
 
298
          self.deck.updateProgress(_(text))
 
299
 
 
300
          if self.META.logToStdOutput:
 
301
            print self.__class__.__name__+ u" - " + dLevels[level].ljust(9) +u' -\t'+ _(text)
 
302
 
 
303
 
 
304
    # OPEN AND LOAD
 
305
    def openAnything(self,source):   
 
306
        "Open any source / actually only openig of files is used"
 
307
 
 
308
        if source == "-":
 
309
            return sys.stdin
 
310
 
 
311
        # try to open with urllib (if source is http, ftp, or file URL)
 
312
        import urllib                         
 
313
        try:                                  
 
314
            return urllib.urlopen(source)   
 
315
        except (IOError, OSError):            
 
316
            pass                              
 
317
 
 
318
        # try to open with native open function (if source is pathname)
 
319
        try:                                  
 
320
            return open(source)           
 
321
        except (IOError, OSError):            
 
322
            pass                              
 
323
 
 
324
        # treat source as string
 
325
        import StringIO                       
 
326
        return StringIO.StringIO(str(source))
 
327
 
 
328
    def loadSource(self, source):
 
329
        """Load source file and parse with xml.dom.minidom"""
 
330
        self.source = source
 
331
        self.logger(u'Load started...')
 
332
        sock = self.openAnything(self.source)
 
333
        self.xmldoc = minidom.parse(sock).documentElement
 
334
        sock.close()
 
335
        self.logger(u'Load done.')
 
336
 
 
337
 
 
338
    # PARSE
 
339
    def parse(self, node=None):          
 
340
        "Parse method - parses document elements"
 
341
 
 
342
        if node==None and self.xmldoc<>None:
 
343
          node = self.xmldoc
 
344
 
 
345
        _method = "parse_%s" % node.__class__.__name__
 
346
        if hasattr(self,_method):
 
347
          parseMethod = getattr(self, _method)
 
348
          parseMethod(node)
 
349
        else:
 
350
          self.logger(u'No handler for method %s' % _method, level=3)
 
351
 
 
352
    def parse_Document(self, node): 
 
353
        "Parse XML document"
 
354
 
 
355
        self.parse(node.documentElement)
 
356
 
 
357
    def parse_Element(self, node): 
 
358
        "Parse XML element"
 
359
 
 
360
        _method = "do_%s" % node.tagName
 
361
        if hasattr(self,_method):
 
362
          handlerMethod = getattr(self, _method)
 
363
          handlerMethod(node)
 
364
        else:
 
365
          self.logger(u'No handler for method %s' % _method, level=3)
 
366
          #print traceback.print_exc()
 
367
 
 
368
    def parse_Text(self, node):    
 
369
        "Parse text inside elements. Text is stored into local buffer."
 
370
 
 
371
        text = node.data
 
372
        self.cntBuf.append(text)
 
373
 
 
374
    #def parse_Comment(self, node):
 
375
    #    """
 
376
    #    Source can contain XML comments, but we ignore them
 
377
    #    """
 
378
    #    pass
 
379
 
 
380
 
 
381
    # DO
 
382
    def do_SuperMemoCollection(self, node): 
 
383
        "Process SM Collection"
 
384
 
 
385
        for child in node.childNodes: self.parse(child)
 
386
 
 
387
    def do_SuperMemoElement(self, node): 
 
388
        "Process SM Element (Type - Title,Topics)"
 
389
 
 
390
        self.logger('='*45, level=3)
 
391
 
 
392
        self.cntElm.append(SuperMemoElement())
 
393
        self.cntElm[-1]['lTitle'] = self.cntMeta['title']
 
394
 
 
395
        #parse all child elements
 
396
        for child in node.childNodes: self.parse(child)
 
397
 
 
398
        #strip all saved strings, just for sure
 
399
        for key in self.cntElm[-1].keys():
 
400
          if hasattr(self.cntElm[-1][key], 'strip'):
 
401
            self.cntElm[-1][key]=self.cntElm[-1][key].strip()
 
402
 
 
403
        #pop current element
 
404
        smel = self.cntElm.pop()
 
405
 
 
406
        # Process cntElm if is valid Item (and not an Topic etc..)
 
407
        # if smel.Lapses != None and smel.Interval != None and smel.Question != None and smel.Answer != None:
 
408
        if smel.Title == None and smel.Question != None and smel.Answer != None:
 
409
          if smel.Answer.strip() !='' and smel.Question.strip() !='':
 
410
            
 
411
            # migrate only memorized otherway skip/continue
 
412
            if self.META.onlyMemorizedItems and not(int(smel.Interval) > 0):
 
413
              self.logger(u'Element skiped  \t- not memorized ...', level=3)
 
414
            else:
 
415
              #import sm element data to Anki
 
416
              self.addItemToCards(smel)
 
417
              self.logger(u"Import element \t- " + smel['Question'], level=3)
 
418
 
 
419
              #print element
 
420
              self.logger('-'*45, level=3)
 
421
              for key in smel.keys():
 
422
                self.logger('\t%s %s' % ((key+':').ljust(15),smel[key]), level=3 )
 
423
          else:
 
424
            self.logger(u'Element skiped  \t- no valid Q and A ...', level=3)
 
425
 
 
426
 
 
427
        else:
 
428
          # now we know that item was topic 
 
429
          # parseing of whole node is now finished
 
430
 
 
431
          # test if it's really topic
 
432
          if smel.Title != None:
 
433
            # remove topic from title list
 
434
            t = self.cntMeta['title'].pop()
 
435
            self.logger(u'End of topic \t- %s' % (t), level=2)
 
436
 
 
437
    def do_Content(self, node): 
 
438
        "Process SM element Content"
 
439
 
 
440
        for child in node.childNodes:
 
441
          if hasattr(child,'tagName') and child.firstChild != None:
 
442
            self.cntElm[-1][child.tagName]=child.firstChild.data
 
443
 
 
444
    def do_LearningData(self, node): 
 
445
        "Process SM element LearningData"
 
446
        
 
447
        for child in node.childNodes:
 
448
          if hasattr(child,'tagName') and child.firstChild != None:
 
449
            self.cntElm[-1][child.tagName]=child.firstChild.data
 
450
 
 
451
    # It's being processed in do_Content now
 
452
    #def do_Question(self, node): 
 
453
    #    for child in node.childNodes: self.parse(child)
 
454
    #    self.cntElm[-1][node.tagName]=self.cntBuf.pop()
 
455
 
 
456
    # It's being processed in do_Content now
 
457
    #def do_Answer(self, node): 
 
458
    #    for child in node.childNodes: self.parse(child)
 
459
    #    self.cntElm[-1][node.tagName]=self.cntBuf.pop()
 
460
 
 
461
    def do_Title(self, node): 
 
462
        "Process SM element Title"
 
463
 
 
464
        t = self._decode_htmlescapes(node.firstChild.data)
 
465
        self.cntElm[-1][node.tagName] = t
 
466
        self.cntMeta['title'].append(t)
 
467
        self.cntElm[-1]['lTitle'] = self.cntMeta['title']
 
468
        self.logger(u'Start of topic \t- ' + u" / ".join(self.cntMeta['title']), level=2)
 
469
 
 
470
 
 
471
    def do_Type(self, node): 
 
472
        "Process SM element Type"
 
473
 
 
474
        if len(self.cntBuf) >=1 :
 
475
          self.cntElm[-1][node.tagName]=self.cntBuf.pop()
 
476
 
 
477
 
 
478
if __name__ == '__main__':
 
479
 
 
480
  # for testing you can start it standalone
 
481
 
 
482
  #file = u'/home/epcim/hg2g/dev/python/sm2anki/ADVENG2EXP.xxe.esc.zaloha_FINAL.xml'
 
483
  #file = u'/home/epcim/hg2g/dev/python/anki/libanki/tests/importing/supermemo/original_ENGLISHFORBEGGINERS_noOEM.xml'
 
484
  #file = u'/home/epcim/hg2g/dev/python/anki/libanki/tests/importing/supermemo/original_ENGLISHFORBEGGINERS_oem_1250.xml'
 
485
  file = str(sys.argv[1])
 
486
  impo = SupermemoXmlImporter(Deck(),file)
 
487
  impo.foreignCards()
 
488
 
 
489
  sys.exit(1)
 
490
 
 
491
# vim: ts=4 sts=2 ft=python