Ejemplo n.º 1
0
 def test__render_data_diff(self):
     item_name = 'Html_Item'
     fqname = split_fqname(item_name)
     empty_html = '<span></span>'
     html = '<span>\ud55c</span>'
     meta = {CONTENTTYPE: 'text/html;charset=utf-8'}
     item = Item.create(item_name)
     item._save(meta, empty_html)
     item = Item.create(item_name)
     # Unicode test, html escaping
     rev1 = update_item(item_name, meta, html)
     rev2 = update_item(item_name, {}, '     ')
     result = Text._render_data_diff(item.content, rev1, rev2, fqname=fqname)
     assert escape(html) in result
     # Unicode test, whitespace
     rev1 = update_item(item_name, {}, '\n\n')
     rev2 = update_item(item_name, {}, '\n     \n')
     result = Text._render_data_diff(item.content, rev1, rev2, fqname=fqname)
     assert '<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>' in result
     # If fairly similar diffs are correctly spanned or not, also check indent
     rev1 = update_item(item_name, {}, 'One Two Three Four\nSix\n\ud55c')
     rev2 = update_item(item_name, {}, 'Two Three Seven Four\nSix\n\ud55c')
     result = Text._render_data_diff(item.content, rev1, rev2, fqname=fqname)
     assert '<span>One </span>Two Three Four' in result
     assert 'Two Three <span>Seven </span>Four' in result
     # Check for diff_html.diff return types
     assert reduce(lambda x, y: x and y, [isinstance(i[1], str) and isinstance(i[3], str) for i in diff_html.diff('One Two Three Four\nSix\n', 'Two Three Seven Four\nSix Seven\n')], True)
Ejemplo n.º 2
0
 def test_namespaces(self):
     item_name_n = 'normal'
     item = self.imw[item_name_n]
     rev_n = item.store_revision(dict(
         name=[
             item_name_n,
         ], contenttype='text/plain;charset=utf-8'),
                                 BytesIO(item_name_n.encode('utf-8')),
                                 return_rev=True)
     item_name_u = '%s/user' % NAMESPACE_USERS
     fqname_u = split_fqname(item_name_u)
     item = self.imw.get_item(**fqname_u.query)
     rev_u = item.store_revision(dict(
         name=[fqname_u.value],
         namespace=fqname_u.namespace,
         contenttype='text/plain;charset=utf-8'),
                                 BytesIO(item_name_u.encode('utf-8')),
                                 return_rev=True)
     item = self.imw[item_name_n]
     rev_n = item.get_revision(rev_n.revid)
     assert rev_n.meta[NAMESPACE] == ''
     assert rev_n.meta[NAME] == [
         item_name_n,
     ]
     item = self.imw[item_name_u]
     rev_u = item.get_revision(rev_u.revid)
     assert rev_u.meta[NAMESPACE] == NAMESPACE_USERS
     assert rev_u.meta[NAME] == [item_name_u.split('/')[1]]
Ejemplo n.º 3
0
    def location_breadcrumbs(self, fqname):
        """
        Split the incoming fqname into segments. Reassemble into a list of tuples.
        If the fqname has a namespace, the first tuple's segment_name will have the
        namespace as a prefix.

        :rtype: list
        :returns: location breadcrumbs items in tuple (segment_name, fq_name, exists)
        """
        breadcrumbs = []
        current_item = ''
        if not isinstance(fqname, CompositeName):
            fqname = split_fqname(fqname)
        if fqname.field != NAME_EXACT:
            return [
                (fqname, fqname, bool(self.storage.get_item(**fqname.query)))
            ]  # flaskg.unprotected_storage.get_item(**fqname.query)
        namespace = segment1_namespace = fqname.namespace
        item_name = fqname.value
        if not item_name:
            return breadcrumbs
        for segment in item_name.split('/'):
            current_item += segment
            fq_current = CompositeName(namespace, NAME_EXACT, current_item)
            fq_segment = CompositeName(segment1_namespace, NAME_EXACT, segment)
            breadcrumbs.append(
                (fq_segment, fq_current,
                 bool(self.storage.get_item(**fq_current.query))))
            current_item += '/'
            segment1_namespace = ''
        return breadcrumbs
Ejemplo n.º 4
0
def modify_acl(item_name):
    fqname = split_fqname(item_name)
    item = Item.create(item_name)
    meta = dict(item.meta)
    old_acl = meta.get(ACL, '')
    new_acl = request.form.get(fqname.fullname)
    is_valid = acl_validate(new_acl)
    if is_valid:
        if new_acl in ('Empty', ''):
            meta[ACL] = ''
        elif new_acl == 'None' and ACL in meta:
            del (meta[ACL])
        else:
            meta[ACL] = new_acl
        try:
            item._save(meta=meta)
        except AccessDenied:
            # superuser viewed item acl report and tried to change acl but lacked admin permission
            flash(
                L_("Failed! Not authorized.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s",
                   item_name=fqname.fullname,
                   acl_rule=old_acl), "error")
            return redirect(url_for('.item_acl_report'))
        flash(
            L_("Success! ACL saved.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s",
               item_name=fqname.fullname,
               acl_rule=new_acl), "info")
    else:
        flash(
            L_("Nothing changed, invalid ACL.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s",
               item_name=fqname.fullname,
               acl_rule=new_acl), "error")
    return redirect(url_for('.item_acl_report'))
Ejemplo n.º 5
0
    def subitem_index(self, fqname):
        """
        Get a list of subitems for the given fqname

        :rtype: list
        :returns: list of item tuples (item_name, item_title, item_mime_type, has_children)
        """
        from moin.items import Item
        if not isinstance(fqname, CompositeName):
            fqname = split_fqname(fqname)
        item = Item.create(fqname.fullname)
        return item.get_mixed_index()
Ejemplo n.º 6
0
 def test_split_fqname(self):
     app.cfg.namespace_mapping = [('', 'default_backend'), ('ns1/', 'default_backend'), ('ns1/ns2/', 'other_backend')]
     tests = [('ns1/ns2/@itemid/SomeItemID', ('ns1/ns2', 'itemid', 'SomeItemID')),
              ('ns3/@itemid/SomeItemID', ('', 'name_exact', 'ns3/@itemid/SomeItemID')),
              ('Page', ('', 'name_exact', 'Page')),
              ('ns1/ns2/@tags/SomeTag', ('ns1/ns2', 'tags', 'SomeTag')),
              ('@tags/SomeTag', ('', 'tags', 'SomeTag')),
              ('ns1/ns2/@notid', ('ns1/ns2', 'name_exact', '@notid')),
              ('ns1/ns2/ns3/Thisisapagename/ns4', ('ns1/ns2', 'name_exact', 'ns3/Thisisapagename/ns4')),
             ]
     for url, (namespace, field, pagename) in tests:
         assert split_fqname(url) == (namespace, field, pagename)
Ejemplo n.º 7
0
 def may(self, fqname, capability, usernames=None):
     if usernames is not None and isinstance(usernames, (bytes, str)):
         # we got a single username (maybe bytes), make a list of str:
         if isinstance(usernames, bytes):
             usernames = usernames.decode('utf-8')
         usernames = [usernames, ]
     # TODO Make sure that fqname must be a CompositeName class instance, not unicode or list.
     fqname = fqname[0] if isinstance(fqname, list) else fqname
     if isinstance(fqname, str):
         fqname = split_fqname(fqname)
     item = self.get_item(**fqname.query)
     allowed = item.allows(capability, user_names=usernames)
     return allowed
Ejemplo n.º 8
0
    def do_show(self, revid):
        """
        Show a blog item and a list of its blog entries below it.

        If tag GET-parameter is defined, the list of blog entries consists only
        of those entries that contain the tag value in their lists of tags.
        """
        # for now it is just one tag=value, later it could be tag=value1&tag=value2&...
        tag = request.values.get('tag')
        prefix = self.name + '/'
        current_timestamp = int(time.time())
        terms = [
            Term(WIKINAME, app.cfg.interwikiname),
            # Only blog entry itemtypes
            Term(ITEMTYPE, ITEMTYPE_BLOG_ENTRY),
            # Only sub items of this item
            Prefix(NAME_EXACT, prefix),
        ]
        if tag:
            terms.append(Term(TAGS, tag))
        query = And(terms)

        def ptime_sort_key(searcher, docnum):
            """
            Compute the publication time key for blog entries sorting.

            If PTIME is not defined, we use MTIME.
            """
            fields = searcher.stored_fields(docnum)
            ptime = fields.get(PTIME, fields[MTIME])
            return ptime

        ptime_sort_facet = FunctionFacet(ptime_sort_key)

        revs = flaskg.storage.search(query,
                                     sortedby=ptime_sort_facet,
                                     reverse=True,
                                     limit=None)
        blog_entry_items = [
            Item.create(rev.name, rev_id=rev.revid) for rev in revs
        ]
        return render_template(
            'blog/main.html',
            item_name=self.name,
            fqname=split_fqname(self.name),
            blog_item=self,
            blog_entry_items=blog_entry_items,
            tag=tag,
            item=self,
        )
Ejemplo n.º 9
0
    def create(cls,
               name=u'',
               itemtype=None,
               contenttype=None,
               rev_id=CURRENT,
               item=None):
        """
        Create a highlevel Item by looking up :name or directly wrapping
        :item and extract the Revision designated by :rev_id revision.

        The highlevel Item is created by creating an instance of Content
        subclass according to the item's contenttype metadata entry; The
        :contenttype argument can be used to override contenttype. It is used
        only when handling +convert (when deciding the contenttype of target
        item), +modify (when creating a new item whose contenttype is not yet
        decided), +diff and +diffraw (to coerce the Content to a common
        super-contenttype of both revisions).

        After that the Content instance, an instance of Item subclass is
        created according to the item's itemtype metadata entry, and the
        previously created Content instance is assigned to its content
        property.
        """
        fqname = split_fqname(name)
        if fqname.field not in UFIELDS:  # Need a unique key to extract stored item.
            raise FieldNotUniqueError("field {0} is not in UFIELDS".format(
                fqname.field))

        rev = get_storage_revision(fqname, itemtype, contenttype, rev_id, item)
        contenttype = rev.meta.get(CONTENTTYPE) or contenttype
        logging.debug(
            "Item {0!r}, got contenttype {1!r} from revision meta".format(
                name, contenttype))
        # logging.debug("Item %r, rev meta dict: %r" % (name, dict(rev.meta)))

        # XXX Cannot pass item=item to Content.__init__ via
        # content_registry.get yet, have to patch it later.
        content = Content.create(contenttype)

        itemtype = rev.meta.get(ITEMTYPE) or itemtype or ITEMTYPE_DEFAULT
        logging.debug(
            "Item {0!r}, got itemtype {1!r} from revision meta".format(
                name, itemtype))

        item = item_registry.get(itemtype, fqname, rev=rev, content=content)
        logging.debug("Item class {0!r} handles {1!r}".format(
            item.__class__, itemtype))

        content.item = item
        return item
Ejemplo n.º 10
0
 def run(self, name, meta_file, data_file, revid):
     fqname = split_fqname(name)
     item = app.storage.get_item(**fqname.query)
     rev = item[revid]
     meta = json.dumps(dict(rev.meta),
                       sort_keys=True,
                       indent=2,
                       ensure_ascii=False)
     meta = meta.encode('utf-8')
     meta_lines = meta.split('\n')
     meta_lines = [x.rstrip() for x in meta_lines]
     meta = '\n'.join(meta_lines)
     with open(meta_file, 'wb') as mf:
         mf.write(meta)
     data = rev.data.read().replace('\r\n', '\n')
     with open(data_file, 'wb') as df:
         df.write(data)
Ejemplo n.º 11
0
def modify_acl(item_name):
    fqname = split_fqname(item_name)
    item = Item.create(item_name)
    meta = dict(item.meta)
    new_acl = request.form.get(fqname.fullname)
    is_valid = acl_validate(new_acl)
    if is_valid:
        if new_acl in ('Empty', ''):
            meta[ACL] = ''
        elif new_acl == 'None' and ACL in meta:
            del(meta[ACL])
        else:
            meta[ACL] = new_acl
        item._save(meta=meta)
        flash(L_("Success! ACL saved.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "info")
    else:
        flash(L_("Nothing changed, invalid ACL.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "error")
    return redirect(url_for('.item_acl_report'))
Ejemplo n.º 12
0
    def run(self, name, meta_file, data_file, revid):
        fqname = split_fqname(name)
        item = app.storage.get_item(**fqname.query)
        rev = item[revid]
        meta = json.dumps(dict(rev.meta),
                          sort_keys=True,
                          indent=2,
                          ensure_ascii=False)
        with open(meta_file, 'w', encoding='utf-8') as mf:
            mf.write(meta)
        if 'charset' in rev.meta['contenttype']:
            # input data will have \r\n line endings, output will have platform dependent line endings
            charset = rev.meta['contenttype'].split('charset=')[1]
            data = rev.data.read().decode(charset)
            lines = data.splitlines()
            lines = '\n'.join(lines)
            with open(data_file, 'w', encoding=charset) as df:
                df.write(lines)
            return

        data = rev.data.read()
        with open(data_file, 'wb') as df:
            df.write(data)
Ejemplo n.º 13
0
    def navibar(self, fqname):
        """
        Assemble the navibar

        :rtype: list
        :returns: list of tuples (css_class, url, link_text, title)
        """
        if not isinstance(fqname, CompositeName):
            fqname = split_fqname(fqname)
        item_name = fqname.value
        current = item_name
        # Process config navi_bar
        items = []
        for cls, endpoint, args, link_text, title in self.cfg.navi_bar:
            if endpoint == "frontend.show_root":
                endpoint = "frontend.show_item"
                root_fqname = fqname.get_root_fqname()
                default_root = app.cfg.root_mapping.get(
                    NAMESPACE_DEFAULT, app.cfg.default_root)
                args[
                    'item_name'] = root_fqname.fullname if fqname.namespace != NAMESPACE_ALL else default_root
                # override link_text to show untranslated <default_root> itemname or <namespace>/<default_root>
                link_text = args['item_name']
            elif endpoint in [
                    "frontend.global_history", "frontend.global_tags"
            ]:
                args['namespace'] = fqname.namespace
                if fqname and fqname.namespace:
                    link_text = '{0}/{1}'.format(fqname.namespace, link_text)
            elif endpoint == "frontend.index":
                args['item_name'] = fqname.namespace
                if fqname and fqname.namespace:
                    link_text = '{0}/{1}'.format(fqname.namespace, link_text)
            elif endpoint == "admin.index" and not getattr(
                    flaskg.user.may, SUPERUSER)():
                continue
            items.append((cls, url_for(endpoint, **args), link_text, title))

        # Add user links to wiki links.
        for text in self.user.quicklinks:
            url, link_text, title = self.split_navilink(text)
            items.append(('userlink', url, link_text, title))

        # Add sister pages (see http://usemod.com/cgi-bin/mb.pl?SisterSitesImplementationGuide )
        for sistername, sisterurl in self.cfg.sistersites:
            if is_local_wiki(sistername):
                items.append(('sisterwiki current', sisterurl, sistername, ''))
            else:
                cid = cache_key(usage="SisterSites", sistername=sistername)
                sisteritems = app.cache.get(cid)
                if sisteritems is None:
                    uo = urllib.request.URLopener()
                    uo.version = 'MoinMoin SisterItem list fetcher 1.0'
                    try:
                        sisteritems = {}
                        f = uo.open(sisterurl)
                        for line in f:
                            line = line.strip()
                            try:
                                item_url, item_name = line.split(' ', 1)
                                sisteritems[item_name.decode(
                                    'utf-8')] = item_url
                            except Exception:
                                pass  # ignore invalid lines
                        f.close()
                        app.cache.set(cid, sisteritems)
                        logging.info(
                            "Site: {0!r} Status: Updated. Pages: {1}".format(
                                sistername, len(sisteritems)))
                    except IOError as err:
                        (title, code, msg, headers) = err.args  # code e.g. 304
                        logging.warning(
                            "Site: {0!r} Status: Not updated.".format(
                                sistername))
                        logging.exception("exception was:")
                if current in sisteritems:
                    url = sisteritems[current]
                    items.append(('sisterwiki', url, sistername, ''))
        return items
Ejemplo n.º 14
0
 def custom_setup(self):
     self.imw = flaskg.unprotected_storage
     self.item_name = "foo"
     self.fqname = split_fqname(self.item_name)
Ejemplo n.º 15
0
    def macro(self, content, arguments, page_url, alternative):

        # defaults
        item = None
        startswith = ""
        regex = None
        ordered = False
        display = "FullPath"
        skiptag = ""

        # process input
        args = []
        if arguments:
            args = arguments[0].split(',')
        for arg in args:
            try:
                key, val = [x.strip() for x in arg.split('=')]
            except ValueError:
                raise ValueError(_('ItemList macro: Argument "%s" does not follow <key>=<val> format (arguments, if more than one, must be comma-separated).' % arg))

            if len(val) < 2 or (val[0] != "'" and val[0] != '"') and val[-1] != val[0]:
                raise ValueError(_("ItemList macro: The key's value must be bracketed by matching quotes."))
            val = val[1:-1]  # strip out the doublequote characters

            if key == "item":
                item = val
            elif key == "startswith":
                startswith = val
            elif key == "regex":
                regex = val
            elif key == "ordered":
                if val == "False":
                    ordered = False
                elif val == "True":
                    ordered = True
                else:
                    raise ValueError(_('ItemList macro: The value must be "True" or "False". (got "%s")' % val))
            elif key == "display":
                display = val  # let 'create_pagelink_list' throw an exception if needed
            elif key == "skiptag":
                skiptag = val
            else:
                raise KeyError(_('ItemList macro: Unrecognized key "%s".' % key))

        # use curr item if not specified
        if item is None:
            item = request.path[1:]
            if item.startswith('+modify/'):
                item = item.split('/', 1)[1]

        # verify item exists and current user has read permission
        if item != "":
            if not flaskg.storage.get_item(**(split_fqname(item).query)):
                # if user lacks read permission, a 403 error was thrown on line above
                raise LookupError(_('ItemList macro: The specified item "%s" does not exist.' % item))

        # process subitems
        children = self.get_item_names(item, startswith=startswith, skiptag=skiptag)
        if regex:
            try:
                regex_re = re.compile(regex, re.IGNORECASE)
            except re.error as err:
                raise ValueError(_("ItemList macro: Error in regex {0!r}: {1}".format(regex, err)))
            newlist = []
            for child in children:
                if regex_re.search(child):
                    newlist.append(child)
            children = newlist
        if not children:
            empty_list = moin_page.list(attrib={moin_page.item_label_generate: ordered and 'ordered' or 'unordered'})
            item_body = moin_page.list_item_body(children=[_("<ItemList macro: No matching items were found.>")])
            item = moin_page.list_item(children=[item_body])
            empty_list.append(item)
            return empty_list
        return self.create_pagelink_list(children, ordered, display)
Ejemplo n.º 16
0
    def macro(self, content, arguments, page_url, alternative):

        # defaults
        item = None
        startswith = ""
        regex = None
        ordered = False
        display = "FullPath"

        # process input
        args = []
        if arguments:
            args = arguments[0].split(',')
        for arg in args:
            try:
                key, val = [x.strip() for x in arg.split('=')]
            except ValueError:
                raise ValueError(
                    _('Argument "%s" does not follow <key>=<val> format (arguments, if more than one, must be comma-separated).'
                      % arg))

            if len(val) < 2 or (val[0] != "'"
                                and val[0] != '"') and val[-1] != val[0]:
                raise ValueError(
                    _("The key's value must be bracketed by matching quotes."))
            val = val[1:-1]  # strip out the doublequote characters

            if key == "item":
                item = val
            elif key == "startswith":
                startswith = val
            elif key == "regex":
                regex = val
            elif key == "ordered":
                if val == "False":
                    ordered = False
                elif val == "True":
                    ordered = True
                else:
                    raise ValueError(
                        _('The value must be "True" or "False". (got "%s")' %
                          val))
            elif key == "display":
                display = val  # let 'create_pagelink_list' throw an exception if needed
            else:
                raise KeyError(_('Unrecognized key "%s".' % key))

        # use curr page if not specified
        if item is None:
            item = request.path[1:]

        # test if item doesn't exist (potentially due to user's ACL, but that doesn't matter)
        if item != "":  # why are we retaining this behavior from PagenameList?
            if not flaskg.storage.get_item(**(split_fqname(item).query)):
                raise LookupError(
                    _('The specified item "%s" does not exist.' % item))

        # process child pages
        children = self.get_item_names(item, startswith)
        if regex:
            try:
                regex_re = re.compile(regex, re.IGNORECASE)
            except re.error as err:
                raise ValueError(
                    _("Error in regex {0!r}: {1}".format(regex, err)))
            newlist = []
            for child in children:
                if regex_re.search(child):
                    newlist.append(child)
            children = newlist
        if not children:
            empty_list = moin_page.list(
                attrib={
                    moin_page.item_label_generate:
                    ordered and 'ordered' or 'unordered'
                })
            item_body = moin_page.list_item_body(
                children=[_("<No matching pages were found>")])
            item = moin_page.list_item(children=[item_body])
            empty_list.append(item)
            return empty_list
        return self.create_pagelink_list(children, ordered, display)
Ejemplo n.º 17
0
    def macro(self, content, arguments, page_url, alternative):

        # find page name of current page
        # to be able to use it as a default page name
        this_page = request.path[1:]
        if this_page.startswith('+modify/'):
            this_page = this_page.split('/', 1)[1]

        # get arguments from the macro if available,
        # arguments will be None for <<MonthCalendar>>
        # and <<MonthCalendar()>>
        args = arguments[0] if arguments else ""

        # get default arguments for year and month
        # and current day for calendar styling
        currentyear = datetime.now().year
        currentmonth = datetime.now().month
        currentday = datetime.now().day

        # parse and check arguments,
        # set default values if necessary
        parmpagename, parmyear, parmmonth, parmoffset, parmheight6, anniversary = \
            parseargs(args, this_page, currentyear, currentmonth, 0, False, False)

        year, month = yearmonthplusoffset(parmyear, parmmonth, parmoffset)

        # get the calendar
        monthcal = calendar.monthcalendar(year, month)

        # european / US differences
        months = (_('January'), _('February'), _('March'), _('April'),
                  _('May'), _('June'), _('July'), _('August'), _('September'),
                  _('October'), _('November'), _('December'))
        # Set things up for Monday or Sunday as the first day of the week
        if calendar.firstweekday() == calendar.MONDAY:
            wkend = (5, 6)
            wkdays = (_('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'),
                      _('Sat'), _('Sun'))
        if calendar.firstweekday() == calendar.SUNDAY:
            wkend = (0, 6)
            wkdays = (_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'),
                      _('Fri'), _('Sat'))

        calcaption = "{} {}".format(months[month - 1], year)
        calhead = []

        r7 = range(7)

        for wkday in r7:
            wday = _(wkdays[wkday])
            if wkday in wkend:
                day_class = "cal-weekend"
            else:
                day_class = "cal-workday"
            calhead.append((wday, day_class))

        # parmheight6 true: show 6 week rows even if month has 5 weeks only
        if parmheight6:
            while len(monthcal) < 6:
                monthcal = monthcal + [[0, 0, 0, 0, 0, 0, 0]]

        calrows = []
        for week in monthcal:
            calweek = []
            for wkday in r7:
                day = week[wkday]
                day_addr = ''
                day_class = 'cal-emptyday'
                if not day:
                    # u'\xa0' is a non-breaking space (just like &nbsp;
                    # in html) to make sure empty cells have the same
                    # height as rows with content
                    calweek.append((u'\xa0', None, 'cal-invalidday'))
                else:
                    # we only process the first calendar (or item name)
                    # mentioned in the macro parameters (in case separate
                    # pages are separated by "*")
                    page = parmpagename[0]

                    if anniversary:
                        link = "{:s}/{:02d}-{:02d}".format(page, month, day)
                    else:
                        link = "{:s}/{:4d}-{:02d}-{:02d}".format(
                            page, year, month, day)
                    day_addr = link

                    if flaskg.storage.get_item(
                            **(split_fqname(day_addr).query)):
                        day_class = "cal-usedday"

                    if day == currentday and month == currentmonth and year == currentyear:
                        day_class = "cal-today"

                    calweek.append((str(day), day_addr, day_class))
            calrows.append(calweek)

        ret = build_dom_calendar_table(rows=calrows,
                                       head=calhead,
                                       caption=calcaption,
                                       cls='calendar')
        return ret