def __call__(self, content): item_name = content # we just give the name of the item in the content attrib = { xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name, query='do=modify'), } return moin_page.a(attrib=attrib, children=[ _("%(item_name)s does not exist. Create it?", item_name=item_name) ])
def handle_wikilocal_links(self, elem, input, page): if input.path: # this can be a relative path, make it absolute: path = input.path path = self.absolute_path(path, page.path) item_name = unicode(path) if not flaskg.storage.has_item(item_name): # XXX these index accesses slow down the link converter quite a bit elem.set(moin_page.class_, 'moin-nonexistent') else: item_name = unicode(page.path[1:]) endpoint, rev, query = self._get_do_rev(input.query) url = url_for_item(item_name, rev=rev, endpoint=endpoint) link = Iri(url, query=query, fragment=input.fragment) elem.set(self._tag_xlink_href, link)
def inline_freelink_repl(self, stack, freelink, freelink_bang=None, freelink_interwiki_page=None, freelink_interwiki_ref=None, freelink_page=None, freelink_email=None): if freelink_bang: stack.top_append(freelink) return attrib = {} if freelink_page: page = freelink_page.encode('utf-8') if '#' in page: path, fragment = page.rsplit('#', 1) else: path, fragment = page, None link = Iri(scheme='wiki.local', path=path, fragment=fragment) text = freelink_page elif freelink_email: link = 'mailto:' + freelink_email text = freelink_email else: if not is_known_wiki(freelink_interwiki_ref): stack.top_append(freelink) return link = Iri(scheme='wiki', authority=freelink_interwiki_ref, path='/' + freelink_interwiki_page) text = freelink_interwiki_page attrib[xlink.href] = link element = moin_page.a(attrib, children=[text]) stack.top_append(element)
def inline_object_repl(self, stack, object, object_page=None, object_url=None, object_text=None): """Handles objects included in the page.""" attrib = {} if object_text: attrib[html.alt] = object_text if object_page is not None: att = 'attachment:' # moin 1.9 needed this for an attached file if object_page.startswith(att): object_page = '/' + object_page[len(att):] # now we have a subitem target = Iri(scheme='wiki.local', path=object_page) attrib[xinclude.href] = target element = xinclude.include(attrib=attrib) else: attrib[xlink.href] = object_url element = moin_page.object(attrib) stack.top_append(element)
def __call__(self, content): item_name = content # we just give the name of the item in the content attrib = { moin_page.type_: unicode(self.input_type), xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name, query='do=get'), } return moin_page.object_( attrib=attrib, children=[ u'Your Browser does not support HTML5 audio/video element.', ])
def handle_wiki_links(self, elem, input): wiki_name = 'Self' if input.authority and input.authority.host: wn = unicode(input.authority.host) if is_known_wiki(wn): # interwiki link elem.set(moin_page.class_, 'moin-interwiki') wiki_name = wn item_name = unicode(input.path[1:]) endpoint, rev, query = self._get_do_rev(input.query) url = url_for_item(item_name, wiki_name=wiki_name, rev=rev, endpoint=endpoint) link = Iri(url, query=query, fragment=input.fragment) elem.set(self._tag_xlink_href, link)
def internal_representation(self, attributes=None): """ Return the internal representation of a document using a DOM Tree """ hash_name = HASH_ALGORITHM hash_hexdigest = self.rev.meta.get(hash_name) if hash_hexdigest: cid = cache_key(usage="internal_representation", hash_name=hash_name, hash_hexdigest=hash_hexdigest) doc = app.cache.get(cid) else: # likely a non-existing item doc = cid = None if doc is None: # We will see if we can perform the conversion: # FROM_mimetype --> DOM # if so we perform the transformation, otherwise we don't from MoinMoin.converter import default_registry as reg input_conv = reg.get(Type(self.contenttype), type_moin_document) if not input_conv: raise TypeError( "We cannot handle the conversion from {0} to the DOM tree". format(self.contenttype)) smiley_conv = reg.get(type_moin_document, type_moin_document, icon='smiley') # We can process the conversion links = Iri(scheme='wiki', authority='', path='/' + self.name) doc = input_conv(self.rev, self.contenttype, arguments=attributes) # XXX is the following assuming that the top element of the doc tree # is a moin_page.page element? if yes, this is the wrong place to do that # as not every doc will have that element (e.g. for images, we just get # moin_page.object, for a tar item, we get a moin_page.table): doc.set(moin_page.page_href, unicode(links)) if self.contenttype.startswith(( u'text/x.moin.wiki', u'text/x-mediawiki', u'text/x.moin.creole', )): doc = smiley_conv(doc) if cid: app.cache.set(cid, doc) return doc
def __call__(self, rev, contenttype=None, arguments=None): item_name = rev.item.name attrib = { moin_page.type_: unicode(self.input_type), xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name, query='do=get&rev={0}'.format(rev.revid)), } obj = moin_page.object_( attrib=attrib, children=[ u'Your Browser does not support HTML5 audio/video element.', ]) body = moin_page.body(children=(obj, )) return moin_page.page(children=(body, ))
def absolute_path(self, path, current_page_path): """ Converts a relative iri path into an absolute one :param path: the relative path to be converted :type path: Iri.path :param current_page_path: the path of the page where the link is :type current_page_path: Iri.path :returns: the absolute equivalent of the relative path :rtype: Iri.path """ quoted_path = path.quoted # starts from 1 because 0 is always / for the current page quoted_current_page_path = current_page_path[1:].quoted abs_path = AbsItemName(quoted_current_page_path, quoted_path) abs_path = Iri(abs_path).path return abs_path
def macro(self, content, arguments, page_url, alternative): request = self.request if arguments: item_count = int(arguments[0]) else: item_count = 1 rootitem = Item(request, u'') all_item_names = [i.name for i in rootitem.list_items()] # Now select random item from the full list, and if it exists and # we can read it, save. random_item_names = [] found = 0 while found < item_count and all_item_names: # Take one random item from the list item_name = random.choice(all_item_names) all_item_names.remove(item_name) # Filter out items the user may not read. try: item = Item.create(item_name) random_item_names.append(item_name) found += 1 except AccessDeniedError: pass if not random_item_names: return random_item_names.sort() result = moin_page.span() for name in random_item_names: link = unicode(Iri(scheme=u'wiki', authority=u'', path=u'/' + name)) result.append(moin_page.a(attrib={xlink.href: link}, children=[name])) result.append(", ") del result[-1] # kill last comma return result
def __call__(self, rev, contenttype=None, arguments=None): item_name = rev.item.name query_keys = {'do': 'get', 'rev': rev.revid} attrib = {} if arguments: query = arguments.keyword.get(xinclude.href).query if query: query_keys.update(url_decode(query)) attrib = arguments.keyword query = url_encode(query_keys, charset=CHARSET, encode_keys=True) attrib.update({ moin_page.type_: unicode(self.input_type), xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name, query=query), }) obj = moin_page.object_(attrib=attrib, children=[item_name, ]) body = moin_page.body(children=(obj, )) return moin_page.page(children=(body, ))
def inline_link_repl(self, stack, link, link_url=None, link_page=None, link_text=None): """Handle all kinds of links.""" if link_page is not None: att = 'attachment:' # moin 1.9 needed this for an attached file if link_page.startswith(att): link_page = '/' + link_page[len(att):] # now we have a subitem target = unicode(Iri(scheme='wiki.local', path=link_page)) text = link_page else: target = link_url text = link_url element = moin_page.a(attrib={xlink.href: target}) stack.push(element) self.parse_inline(link_text or text, stack, self.link_desc_re) stack.pop()
def inline_link_repl(self, stack, link, link_url=None, link_item=None, link_args=None, external_link_url=None, alt_text=''): """Handle all kinds of links.""" link_text = '' if link_args and len(link_args.split('|')) > 2: link_args = parse_arguments(' '.join(link_args.split( '|')[:-1])) # TODO needs parsing for mediawiki_args query = url_encode(link_args.keyword, charset=config.charset, encode_keys=True) else: if link_args: link_text = link_args.split('|')[-1] link_args = parse_arguments(' '.join( link_args.split('|')[:-1])) query = None if link_item is not None: if '#' in link_item: path, fragment = link_item.rsplit('#', 1) else: path, fragment = link_item, None target = Iri(scheme='wiki.local', path=path, query=query, fragment=fragment) text = link_item else: if link_url and len(link_url.split(':')) > 0 and link_url.split( ':')[0] == 'File': object_item = ':'.join(link_url.split(':')[1:]) args = link_args.keyword if object_item is not None: if 'do' not in args: # by default, we want the item's get url for transclusion of raw data: args['do'] = 'get' query = url_encode(args, charset=config.charset, encode_keys=True) target = Iri(scheme='wiki.local', path=object_item, query=query, fragment=None) text = object_item else: target = Iri(object_url) text = object_url attrib = {xlink.href: target} if link_text is not None: attrib[moin_page.alt] = link_text element = moin_page.object(attrib) stack.push(element) if link_text: self.preprocessor.push() self.parse_inline(link_text, stack, self.inlinedesc_re) self.preprocessor.pop() else: stack.top_append(text) stack.pop() return target = Iri(link_url) text = link_url if external_link_url: target = Iri(external_link_url) text = alt_text element = moin_page.a(attrib={xlink.href: target}) stack.push(element) if link_text: self.preprocessor.push() self.parse_inline(link_text, stack, self.inlinedesc_re) self.preprocessor.pop() else: stack.top_append(text) stack.pop()
def allowed_uri_scheme(uri): parsed = Iri(uri) return not parsed.scheme or parsed.scheme in URI_SCHEMES
def recurse(self, elem, page_href): # on first call, elem.tag.name=='page'. # Descendants (body, div, p, include, page, etc.) are processed by recursing through DOM # stack is used to detect transclusion loops page_href_new = elem.get(self.tag_page_href) if page_href_new: page_href_new = Iri(page_href_new) if page_href_new != page_href: page_href = page_href_new self.stack.append(page_href) else: self.stack.append(None) else: self.stack.append(None) try: if elem.tag == self.tag_xi_include: # we have already recursed several levels and found a transclusion: "{{SomePage}}" or similar # process the transclusion and add it to the DOM. Subsequent recursions will traverse through # the transclusion's elements. href = elem.get(self.tag_xi_href) xpointer = elem.get(self.tag_xi_xpointer) xp_include_pages = None xp_include_sort = None xp_include_items = None xp_include_skipitems = None xp_include_heading = None xp_include_level = None if xpointer: xp = XPointer(xpointer) xp_include = None xp_namespaces = {} for entry in xp: uri = None name = entry.name.split(':', 1) if len(name) > 1: prefix, name = name uri = xp_namespaces.get(prefix, False) else: name = name[0] if uri is None and name == 'xmlns': d_prefix, d_uri = entry.data.split('=', 1) xp_namespaces[d_prefix] = d_uri elif uri == moin_page.namespace and name == 'include': xp_include = XPointer(entry.data) if xp_include: for entry in xp_include: name, data = entry.name, entry.data_unescape if name == 'pages': xp_include_pages = data elif name == 'sort': xp_include_sort = data elif name == 'items': xp_include_items = int(data) elif name == 'skipitems': xp_include_skipitems = int(data) elif name == 'heading': xp_include_heading = data elif name == 'level': xp_include_level = data if href: # We have a single page to transclude href = Iri(href) link = Iri(scheme='wiki', authority='') if href.scheme == 'wiki': if href.authority: raise ValueError("can't handle xinclude for non-local authority") else: path = href.path[1:] elif href.scheme == 'wiki.local': page = page_href path = href.path if path[0] == '': # /subitem tmp = page.path[1:] tmp.extend(path[1:]) path = tmp elif path[0] == '..': # ../sisteritem path = page.path[1:] + path[1:] else: raise ValueError("can't handle xinclude for schemes other than wiki or wiki.local") link.path = path if flaskg.user.may.read(unicode(path)): page = Item.create(unicode(path)) pages = ((page, link), ) else: # ACLs prevent user from viewing a transclusion - show message message = moin_page.p(children=(_('Access Denied, transcluded content suppressed.'))) attrib = {html.class_: 'warning'} div = ET.Element(moin_page.div, attrib, children=(message, )) container = ET.Element(moin_page.body, children=(div, )) return [container, 0] # replace transclusion with container's child elif xp_include_pages: # XXX we currently interpret xp_include_pages as wildcard, but it should be regex # for compatibility with moin 1.9. whoosh has upcoming regex support, but it is not # released yet. if xp_include_pages.startswith('^'): # get rid of the leading ^ the Include macro needed to get into "regex mode" xp_include_pages = xp_include_pages[1:] query = And([Term(WIKINAME, app.cfg.interwikiname), Wildcard(NAME_EXACT, xp_include_pages)]) reverse = xp_include_sort == 'descending' results = flaskg.storage.search(query, sortedby=NAME_EXACT, reverse=reverse, limit=None) pagelist = [result[NAME] for result in results] if xp_include_skipitems is not None: pagelist = pagelist[xp_include_skipitems:] if xp_include_items is not None: pagelist = pagelist[xp_include_items + 1:] pages = ((Item.create(p), Iri(scheme='wiki', authority='', path='/' + p)) for p in pagelist) included_elements = [] for page, p_href in pages: if p_href.path[0] != '/': p_href.path = IriPath('/' + '/'.join(p_href.path)) if p_href in self.stack: # we have a transclusion loop, create an error message showing list of pages forming loop loop = self.stack[self.stack.index(p_href):] loop = [u'{0}'.format(ref.path[1:]) for ref in loop if ref is not None] + [page.name] msg = u'Error: Transclusion loop via: ' + u', '.join(loop) attrib = {html.class_: 'moin-error'} strong = ET.Element(moin_page.strong, attrib, (msg, )) included_elements.append(strong) continue # TODO: Is this correct? if not flaskg.user.may.read(page.name): continue if xp_include_heading is not None: attrib = {self.tag_href: p_href} children = (xp_include_heading or page.name, ) elem_a = ET.Element(self.tag_a, attrib, children=children) attrib = {self.tag_outline_level: xp_include_level or '1'} elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, )) included_elements.append(elem_h) page_doc = page.content.internal_representation(attributes=Arguments(keyword=elem.attrib)) self.recurse(page_doc, page_href) # The href needs to be an absolute URI, without the prefix "wiki://" page_doc = mark_item_as_transclusion(page_doc, p_href.path) included_elements.append(page_doc) if len(included_elements) > 1: # use a div as container result = ET.Element(self.tag_div) result.extend(included_elements) elif included_elements: result = included_elements[0] else: result = None # end of processing for transclusion; the "result" will get inserted into the DOM below return result # Traverse the DOM by calling self.recurse with each child of the current elem. # Starting elem.tag.name=='page'. container = [] i = 0 while i < len(elem): child = elem[i] if isinstance(child, ET.Node): ret = self.recurse(child, page_href) if ret: # Either child or a descendant of child is a transclusion. # See top of this script for notes on why these DOM adjustments are required. if isinstance(ret, ET.Node) and elem.tag.name in NO_BLOCK_CHILDREN: body = ret[0] if len(body) == 0: # the transcluded item is empty, insert an empty span into DOM attrib = Attributes(ret).convert() elem[i] = ET.Element(moin_page.span, attrib=attrib) elif (isinstance(body[0], ET.Node) and (len(body) > 1 or body[0].tag.name not in ('p', 'object', 'a'))): # Complex case: "some text {{BlockItem}} more text" or "\n{{BlockItem}}\n" where # the BlockItem body contains multiple p's, a table, preformatted text, etc. # These block elements cannot be made a child of the current elem, so we create # a container to replace elem. # Create nodes to hold any siblings before and after current child (elem[i]) before = copy.deepcopy(elem) after = copy.deepcopy(elem) before[:] = elem[0:i] after[:] = elem[i + 1:] if len(before): # there are siblings before transclude, save them in container container.append(before) new_trans_ptr = len(container) # get attributes from page node; # we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"} attrib = Attributes(ret).convert() # current elem will likely be replaced by container so we need to copy data-lineno attr if html.data_lineno in elem.attrib: attrib[html.data_lineno] = elem.attrib[html.data_lineno] # make new div node to hold transclusion, copy children, and save in container div = ET.Element(moin_page.div, attrib=attrib, children=body[:]) container.append(div) # new_trans_ptr is index to this if len(after): container.append(after) if elem.tag.name == 'a': # invalid input [[MyPage|{{BlockItem}}]], # best option is to retain A-tag and fail html validation # TODO: error may not be obvious to user - add error message elem[i] = div else: # move up 1 level in recursion where elem becomes the child and # is usually replaced by container return [container, new_trans_ptr] else: # default action for inline transclusions or odd things like circular transclusion error messages classes = child.attrib.get(html.class_, '').split() classes += ret.attrib.get(html.class_, '').split() ret.attrib[html.class_] = ' '.join(classes) elem[i] = ret elif isinstance(ret, types.ListType): # a container has been returned. # Note: there are multiple places where a container may be constructed ret_container, trans_ptr = ret # trans_ptr points to the transclusion within ret_container. # Here the transclusion will always contain a block level element if elem.tag.name in NO_BLOCK_CHILDREN: # Complex case, transclusion effects grand-parent, great-grand-parent, e.g.: # "/* comment {{BlockItem}} */" or "text ''italic {{BlockItem}} italic'' text" # elem is an inline element, build a bigger container to replace elem's parent, before = copy.deepcopy(elem) after = copy.deepcopy(elem) before[:] = elem[0:i] + ret_container[0:trans_ptr] after[:] = ret_container[trans_ptr + 1:] + elem[i + 1:] if len(before): container.append(before) new_trans_ptr = len(container) # child may have classes like "comment" that must be added to transcluded element classes = child.attrib.get(moin_page.class_, '').split() # must use moin_page.class_ above, but use html.class below per html_out.py code classes += ret_container[trans_ptr].attrib.get(html.class_, '').split() ret_container[trans_ptr].attrib[html.class_] = ' '.join(classes) container.append(ret_container[trans_ptr]) # the transclusion if len(after): container.append(after) return [container, new_trans_ptr] else: # elem is a block element for grandchild in child: if isinstance(grandchild, ET.Node) and grandchild.tag.name == u'include': # the include may have classes that must be added to transcluded element classes = grandchild.attrib.get(html.class_, '').split() classes += ret_container[trans_ptr].attrib.get(html.class_, '').split() ret_container[trans_ptr].attrib[html.class_] = ' '.join(classes) # replace child element with the container generated in lower recursion elem[i:i + 1] = ret_container # elem[i] is the child else: # default action for any ret not fitting special cases above, # e.g. tranclusion is within a table cell elem[i] = ret # we are finished with this child, advance to next sibling i += 1 finally: self.stack.pop()
def recurse(self, elem, page_href): # Check if we reached a new page page_href_new = elem.get(self.tag_page_href) if page_href_new: page_href_new = Iri(page_href_new) if page_href_new != page_href: page_href = page_href_new self.stack.append(page_href) else: self.stack.append(None) else: self.stack.append(None) try: if elem.tag == self.tag_xi_include: href = elem.get(self.tag_xi_href) xpointer = elem.get(self.tag_xi_xpointer) xp_include_pages = None xp_include_sort = None xp_include_items = None xp_include_skipitems = None xp_include_heading = None xp_include_level = None if xpointer: xp = XPointer(xpointer) xp_include = None xp_namespaces = {} for entry in xp: uri = None name = entry.name.split(':', 1) if len(name) > 1: prefix, name = name uri = xp_namespaces.get(prefix, False) else: name = name[0] if uri is None and name == 'xmlns': d_prefix, d_uri = entry.data.split('=', 1) xp_namespaces[d_prefix] = d_uri elif uri == moin_page.namespace and name == 'include': xp_include = XPointer(entry.data) if xp_include: for entry in xp_include: name, data = entry.name, entry.data if name == 'pages': xp_include_pages = data elif name == 'sort': xp_include_sort = data elif name == 'items': xp_include_items = int(data) elif name == 'skipitems': xp_include_skipitems = int(data) elif name == 'heading': xp_include_heading = data elif name == 'level': xp_include_level = data if href: # We have a single page to include href = Iri(href) link = Iri(scheme='wiki', authority='') if href.scheme == 'wiki': if href.authority: raise ValueError( "can't handle xinclude for non-local authority" ) else: path = href.path[1:] elif href.scheme == 'wiki.local': page = page_href path = href.path if path[0] == '': # /subitem tmp = page.path[1:] tmp.extend(path[1:]) path = tmp elif path[0] == '..': # ../sisteritem path = page.path[1:] + path[1:] else: raise ValueError( "can't handle xinclude for schemes other than wiki or wiki.local" ) link.path = path page = Item.create(unicode(path)) pages = ((page, link), ) elif xp_include_pages: # We have a regex of pages to include from MoinMoin.search.term import NameFn inc_match = re.compile(xp_include_pages) root_item = Item(name=u'') pagelist = [ item.name for item in root_item.list_items(NameFn(inc_match)) ] pagelist.sort() if xp_include_sort == 'descending': pagelist.reverse() if xp_include_skipitems is not None: pagelist = pagelist[xp_include_skipitems:] if xp_include_items is not None: pagelist = pagelist[xp_include_items + 1:] pages = ((Item.create(p), Iri(scheme='wiki', authority='', path='/' + p)) for p in pagelist) included_elements = [] for page, page_href in pages: if page_href in self.stack: w = ( '<p xmlns="%s"><strong class="error">Recursive include of "%s" forbidden</strong></p>' % (html.namespace, page.name)) div.append(ET.XML(w)) continue # TODO: Is this correct? if not flaskg.user.may.read(page.name): continue if xp_include_heading is not None: attrib = {self.tag_href: page_href} children = (xp_include_heading or page.name, ) elem_a = ET.Element(self.tag_a, attrib, children=children) attrib = { self.tag_outline_level: xp_include_level or '1' } elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, )) div.append(elem_h) page_doc = page.internal_representation() # page_doc.tag = self.tag_div # XXX why did we have this? self.recurse(page_doc, page_href) # Wrap the page with the overlay, but only if it's a "page", or "a". # The href needs to be an absolute URI, without the prefix "wiki://" if page_doc.tag.endswith("page") or page_doc.tag.endswith( "a"): page_doc = wrap_object_with_overlay( page_doc, href=unicode(page_href.path)) included_elements.append(page_doc) if len(included_elements) > 1: # use a div as container result = ET.Element(self.tag_div) result.extend(included_elements) elif included_elements: result = included_elements[0] else: result = None return result for i in xrange(len(elem)): child = elem[i] if isinstance(child, ET.Node): ret = self.recurse(child, page_href) if ret: elem[i] = ret finally: self.stack.pop()
def recurse(self, elem, page_href): # on first call, elem.tag.name=='page'. # Descendants (body, div, p, include, page, etc.) are processed by recursing through DOM # stack is used to detect transclusion loops page_href_new = elem.get(self.tag_page_href) if page_href_new: page_href_new = Iri(page_href_new) if page_href_new != page_href: page_href = page_href_new self.stack.append(page_href) else: self.stack.append(None) else: self.stack.append(None) try: if elem.tag == self.tag_xi_include: # we have already recursed several levels and found a transclusion: "{{SomePage}}" or similar # process the transclusion and add it to the DOM. Subsequent recursions will traverse through # the transclusion's elements. href = elem.get(self.tag_xi_href) xpointer = elem.get(self.tag_xi_xpointer) xp_include_pages = None xp_include_sort = None xp_include_items = None xp_include_skipitems = None xp_include_heading = None xp_include_level = None if xpointer: xp = XPointer(xpointer) xp_include = None xp_namespaces = {} for entry in xp: uri = None name = entry.name.split(':', 1) if len(name) > 1: prefix, name = name uri = xp_namespaces.get(prefix, False) else: name = name[0] if uri is None and name == 'xmlns': d_prefix, d_uri = entry.data.split('=', 1) xp_namespaces[d_prefix] = d_uri elif uri == moin_page.namespace and name == 'include': xp_include = XPointer(entry.data) if xp_include: for entry in xp_include: name, data = entry.name, entry.data_unescape if name == 'pages': xp_include_pages = data elif name == 'sort': xp_include_sort = data elif name == 'items': xp_include_items = int(data) elif name == 'skipitems': xp_include_skipitems = int(data) elif name == 'heading': xp_include_heading = data elif name == 'level': xp_include_level = data if href: # We have a single page to transclude href = Iri(href) link = Iri(scheme='wiki', authority='') if href.scheme == 'wiki': if href.authority: raise ValueError( "can't handle xinclude for non-local authority" ) else: path = href.path[1:] elif href.scheme == 'wiki.local': page = page_href path = href.path if path[0] == '': # /subitem tmp = page.path[1:] tmp.extend(path[1:]) path = tmp elif path[0] == '..': # ../sisteritem path = page.path[1:] + path[1:] else: raise ValueError( "can't handle xinclude for schemes other than wiki or wiki.local" ) link.path = path if flaskg.user.may.read(unicode(path)): page = Item.create(unicode(path)) pages = ((page, link), ) else: # ACLs prevent user from viewing a transclusion - show message message = moin_page.p(children=(_( 'Access Denied, transcluded content suppressed.'))) attrib = {html.class_: 'warning'} div = ET.Element(moin_page.div, attrib, children=(message, )) container = ET.Element(moin_page.body, children=(div, )) return [ container, 0 ] # replace transclusion with container's child elif xp_include_pages: # XXX we currently interpret xp_include_pages as wildcard, but it should be regex # for compatibility with moin 1.9. whoosh has upcoming regex support, but it is not # released yet. if xp_include_pages.startswith('^'): # get rid of the leading ^ the Include macro needed to get into "regex mode" xp_include_pages = xp_include_pages[1:] query = And([ Term(WIKINAME, app.cfg.interwikiname), Wildcard(NAME_EXACT, xp_include_pages) ]) reverse = xp_include_sort == 'descending' results = flaskg.storage.search(query, sortedby=NAME_EXACT, reverse=reverse, limit=None) pagelist = [result[NAME] for result in results] if xp_include_skipitems is not None: pagelist = pagelist[xp_include_skipitems:] if xp_include_items is not None: pagelist = pagelist[xp_include_items + 1:] pages = ((Item.create(p), Iri(scheme='wiki', authority='', path='/' + p)) for p in pagelist) included_elements = [] for page, p_href in pages: if p_href.path[0] != '/': p_href.path = IriPath('/' + '/'.join(p_href.path)) if p_href in self.stack: # we have a transclusion loop, create an error message showing list of pages forming loop loop = self.stack[self.stack.index(p_href):] loop = [ u'{0}'.format(ref.path[1:]) for ref in loop if ref is not None ] + [page.name] msg = u'Error: Transclusion loop via: ' + u', '.join( loop) attrib = {html.class_: 'moin-error'} strong = ET.Element(moin_page.strong, attrib, (msg, )) included_elements.append(strong) continue # TODO: Is this correct? if not flaskg.user.may.read(page.name): continue if xp_include_heading is not None: attrib = {self.tag_href: p_href} children = (xp_include_heading or page.name, ) elem_a = ET.Element(self.tag_a, attrib, children=children) attrib = { self.tag_outline_level: xp_include_level or '1' } elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, )) included_elements.append(elem_h) page_doc = page.content.internal_representation( attributes=Arguments(keyword=elem.attrib)) self.recurse(page_doc, page_href) # The href needs to be an absolute URI, without the prefix "wiki://" page_doc = mark_item_as_transclusion(page_doc, p_href.path) included_elements.append(page_doc) if len(included_elements) > 1: # use a div as container result = ET.Element(self.tag_div) result.extend(included_elements) elif included_elements: result = included_elements[0] else: result = None # end of processing for transclusion; the "result" will get inserted into the DOM below return result # Traverse the DOM by calling self.recurse with each child of the current elem. # Starting elem.tag.name=='page'. container = [] i = 0 while i < len(elem): child = elem[i] if isinstance(child, ET.Node): ret = self.recurse(child, page_href) if ret: # Either child or a descendant of child is a transclusion. # See top of this script for notes on why these DOM adjustments are required. if isinstance(ret, ET.Node ) and elem.tag.name in NO_BLOCK_CHILDREN: body = ret[0] if len(body) == 0: # the transcluded item is empty, insert an empty span into DOM attrib = Attributes(ret).convert() elem[i] = ET.Element(moin_page.span, attrib=attrib) elif (isinstance(body[0], ET.Node) and (len(body) > 1 or body[0].tag.name not in ('p', 'object', 'a'))): # Complex case: "some text {{BlockItem}} more text" or "\n{{BlockItem}}\n" where # the BlockItem body contains multiple p's, a table, preformatted text, etc. # These block elements cannot be made a child of the current elem, so we create # a container to replace elem. # Create nodes to hold any siblings before and after current child (elem[i]) before = copy.deepcopy(elem) after = copy.deepcopy(elem) before[:] = elem[0:i] after[:] = elem[i + 1:] if len(before): # there are siblings before transclude, save them in container container.append(before) new_trans_ptr = len(container) # get attributes from page node; # we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"} attrib = Attributes(ret).convert() # current elem will likely be replaced by container so we need to copy data-lineno attr if html.data_lineno in elem.attrib: attrib[html.data_lineno] = elem.attrib[ html.data_lineno] # make new div node to hold transclusion, copy children, and save in container div = ET.Element(moin_page.div, attrib=attrib, children=body[:]) container.append( div) # new_trans_ptr is index to this if len(after): container.append(after) if elem.tag.name == 'a': # invalid input [[MyPage|{{BlockItem}}]], # best option is to retain A-tag and fail html validation # TODO: error may not be obvious to user - add error message elem[i] = div else: # move up 1 level in recursion where elem becomes the child and # is usually replaced by container return [container, new_trans_ptr] else: # default action for inline transclusions or odd things like circular transclusion error messages classes = child.attrib.get(html.class_, '').split() classes += ret.attrib.get(html.class_, '').split() ret.attrib[html.class_] = ' '.join(classes) elem[i] = ret elif isinstance(ret, types.ListType): # a container has been returned. # Note: there are multiple places where a container may be constructed ret_container, trans_ptr = ret # trans_ptr points to the transclusion within ret_container. # Here the transclusion will always contain a block level element if elem.tag.name in NO_BLOCK_CHILDREN: # Complex case, transclusion effects grand-parent, great-grand-parent, e.g.: # "/* comment {{BlockItem}} */" or "text ''italic {{BlockItem}} italic'' text" # elem is an inline element, build a bigger container to replace elem's parent, before = copy.deepcopy(elem) after = copy.deepcopy(elem) before[:] = elem[0:i] + ret_container[ 0:trans_ptr] after[:] = ret_container[trans_ptr + 1:] + elem[i + 1:] if len(before): container.append(before) new_trans_ptr = len(container) # child may have classes like "comment" that must be added to transcluded element classes = child.attrib.get( moin_page.class_, '').split() # must use moin_page.class_ above, but use html.class below per html_out.py code classes += ret_container[trans_ptr].attrib.get( html.class_, '').split() ret_container[trans_ptr].attrib[ html.class_] = ' '.join(classes) container.append(ret_container[trans_ptr] ) # the transclusion if len(after): container.append(after) return [container, new_trans_ptr] else: # elem is a block element for grandchild in child: if isinstance( grandchild, ET.Node ) and grandchild.tag.name == u'include': # the include may have classes that must be added to transcluded element classes = grandchild.attrib.get( html.class_, '').split() classes += ret_container[ trans_ptr].attrib.get( html.class_, '').split() ret_container[trans_ptr].attrib[ html.class_] = ' '.join(classes) # replace child element with the container generated in lower recursion elem[i:i + 1] = ret_container # elem[i] is the child else: # default action for any ret not fitting special cases above, # e.g. tranclusion is within a table cell elem[i] = ret # we are finished with this child, advance to next sibling i += 1 finally: self.stack.pop()
def test_wiki(app, conv, input_, output): assert 'MoinMoin' in app.cfg.interwiki_map elem = ET.Element(None) conv.handle_wiki_links(elem, Iri(input_)) assert elem.get(xlink.href) == output
def _do_wikilocal(self, input, page, output, skip=None): if skip: py.test.skip(skip) elem = ET.Element(None) self.conv.handle_wikilocal_links(elem, Iri(input), Iri(page)) assert elem.get(xlink.href) == output
def setup_class(self): root = url_root = Iri('./') self.converter = ConverterItemRefs(url_root=root)
def setup_class(self): url_root = Iri('./') self.conv = ConverterExternOutput(url_root=url_root)
def test_wikilocal(conv, input_, page, output): elem = ET.Element(None) conv.handle_wikilocal_links(elem, Iri(input_), Iri(page)) assert elem.get(xlink.href) == output
def inline_link_repl(self, stack, link, link_url=None, link_item=None, link_args=u'', external_link_url=None, alt_text=u''): """Handle all kinds of links.""" link_text = '' link_args_list = [] # Remove the first pipe/space, example of link_args : |arg1|arg2 or " arg1 arg2" parsed_args = self.parse_args(link_args[1:]) query = None if parsed_args.keyword: query = url_encode(parsed_args.keyword, charset=CHARSET, encode_keys=True) # Take the last of positional parameters as link_text(caption) if parsed_args.positional: link_text = parsed_args.positional.pop() if link_item is not None: if '#' in link_item: path, fragment = link_item.rsplit('#', 1) else: path, fragment = link_item, None target = Iri(scheme='wiki.local', path=path, query=query, fragment=fragment) text = link_item else: if link_url and len(link_url.split(':')) > 0 and link_url.split(':')[0] == 'File': object_item = ':'.join(link_url.split(':')[1:]) args = parsed_args.keyword if object_item is not None: if 'do' not in args: # by default, we want the item's get url for transclusion of raw data: args['do'] = 'get' query = url_encode(args, charset=CHARSET, encode_keys=True) target = Iri(scheme='wiki.local', path=object_item, query=query, fragment=None) text = object_item else: target = Iri(scheme='wiki.local', path=object_url) text = object_url if not link_text: link_text = text attrib = {xlink.href: target} attrib[moin_page.alt] = link_text element = moin_page.object(attrib) stack.push(element) if link_text: self.preprocessor.push() self.parse_inline(link_text, stack, self.inlinedesc_re) self.preprocessor.pop() else: stack.top_append(text) stack.pop() return target = Iri(scheme='wiki.local', path=link_url) text = link_url if external_link_url: target = Iri(external_link_url) text = alt_text element = moin_page.a(attrib={xlink.href: target}) stack.push(element) if link_text: self.preprocessor.push() self.parse_inline(link_text, stack, self.inlinedesc_re) self.preprocessor.pop() else: stack.top_append(text) stack.pop()
def recurse(self, elem, page_href): # on first call, elem.tag.name=='page'. Decendants (body, div, p, include, page, etc.) are processed by recursing through DOM # stack is used to detect transclusion loops page_href_new = elem.get(self.tag_page_href) if page_href_new: page_href_new = Iri(page_href_new) if page_href_new != page_href: page_href = page_href_new self.stack.append(page_href) else: self.stack.append(None) else: self.stack.append(None) try: if elem.tag == self.tag_xi_include: # we have already recursed several levels and found a transclusion: "{{SomePage}}" or similar # process the transclusion and add it to the DOM. Subsequent recursions will traverse through the transclusion's elements. href = elem.get(self.tag_xi_href) xpointer = elem.get(self.tag_xi_xpointer) xp_include_pages = None xp_include_sort = None xp_include_items = None xp_include_skipitems = None xp_include_heading = None xp_include_level = None if xpointer: xp = XPointer(xpointer) xp_include = None xp_namespaces = {} for entry in xp: uri = None name = entry.name.split(':', 1) if len(name) > 1: prefix, name = name uri = xp_namespaces.get(prefix, False) else: name = name[0] if uri is None and name == 'xmlns': d_prefix, d_uri = entry.data.split('=', 1) xp_namespaces[d_prefix] = d_uri elif uri == moin_page.namespace and name == 'include': xp_include = XPointer(entry.data) if xp_include: for entry in xp_include: name, data = entry.name, entry.data_unescape if name == 'pages': xp_include_pages = data elif name == 'sort': xp_include_sort = data elif name == 'items': xp_include_items = int(data) elif name == 'skipitems': xp_include_skipitems = int(data) elif name == 'heading': xp_include_heading = data elif name == 'level': xp_include_level = data if href: # We have a single page to transclude href = Iri(href) link = Iri(scheme='wiki', authority='') if href.scheme == 'wiki': if href.authority: raise ValueError("can't handle xinclude for non-local authority") else: path = href.path[1:] elif href.scheme == 'wiki.local': page = page_href path = href.path if path[0] == '': # /subitem tmp = page.path[1:] tmp.extend(path[1:]) path = tmp elif path[0] == '..': # ../sisteritem path = page.path[1:] + path[1:] else: raise ValueError("can't handle xinclude for schemes other than wiki or wiki.local") link.path = path page = Item.create(unicode(path)) pages = ((page, link), ) elif xp_include_pages: # XXX we currently interpret xp_include_pages as wildcard, but it should be regex # for compatibility with moin 1.9. whoosh has upcoming regex support, but it is not # released yet. if xp_include_pages.startswith('^'): # get rid of the leading ^ the Include macro needed to get into "regex mode" xp_include_pages = xp_include_pages[1:] query = And([Term(WIKINAME, app.cfg.interwikiname), Wildcard(NAME_EXACT, xp_include_pages)]) reverse = xp_include_sort == 'descending' results = flaskg.storage.search(query, sortedby=NAME_EXACT, reverse=reverse, limit=None) pagelist = [result[NAME] for result in results] if xp_include_skipitems is not None: pagelist = pagelist[xp_include_skipitems:] if xp_include_items is not None: pagelist = pagelist[xp_include_items + 1:] pages = ((Item.create(p), Iri(scheme='wiki', authority='', path='/' + p)) for p in pagelist) included_elements = [] for page, p_href in pages: if p_href.path[0] != '/': p_href.path = IriPath('/' + '/'.join(p_href.path)) if p_href in self.stack: # we have a transclusion loop, create an error message showing list of pages forming loop loop = self.stack[self.stack.index(p_href):] loop = [u'{0}'.format(ref.path[1:]) for ref in loop if ref is not None] + [page.name] msg = u'Error: Transclusion loop via: ' + u', '.join(loop) attrib = {getattr(moin_page, 'class'): 'moin-error'} strong = ET.Element(moin_page.strong, attrib, (msg, )) included_elements.append(strong) continue # TODO: Is this correct? if not flaskg.user.may.read(page.name): continue if xp_include_heading is not None: attrib = {self.tag_href: p_href} children = (xp_include_heading or page.name, ) elem_a = ET.Element(self.tag_a, attrib, children=children) attrib = {self.tag_outline_level: xp_include_level or '1'} elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, )) included_elements.append(elem_h) page_doc = page.internal_representation() # page_doc.tag = self.tag_div # XXX why did we have this? self.recurse(page_doc, page_href) # if this is an existing item, mark it as a transclusion. non-existent items are not marked (page_doc.tag.name == u'a') # The href needs to be an absolute URI, without the prefix "wiki://" if page_doc.tag.name == u'page': page_doc = mark_item_as_transclusion(page_doc, p_href.path) included_elements.append(page_doc) if len(included_elements) > 1: # use a div as container result = ET.Element(self.tag_div) result.extend(included_elements) elif included_elements: result = included_elements[0] else: result = None # end of processing for transclusion; the "result" will get inserted into the DOM below return result # Traverse the DOM by calling self.recurse with each child of the current elem. Starting elem.tag.name=='page'. container = [] i = 0 while i < len(elem): child = elem[i] if isinstance(child, ET.Node): # almost everything in the DOM will be an ET.Node, exceptions are unicode nodes under p nodes ret = self.recurse(child, page_href) if ret: # "Normally" we are here because child.tag.name==include and ret is a transcluded item (ret.tag.name=page, image, or object, etc.) # that must be inserted into the DOM replacing elem[i]. # This is complicated by the DOM having many inclusions, such as "\n{{SomePage}}\n" that are a child of a "p". # To prevent generation of invalid HTML5 (e.g. "<p>text<p>text</p></p>"), the DOM must be adjusted. if isinstance(ret, types.ListType): # the transclusion may be a return of the container variable from below, add to DOM replacing the current node elem[i:i+1] = ret elif elem.tag.name == 'p': # ancestor P nodes with tranclusions have special case issues, we may need to mangle the ret body = ret[0] # check for instance where ret is a page, ret[0] a body, ret[0][0] a P if not isinstance(body, unicode) and ret.tag.name == 'page' and body.tag.name == 'body' and \ len(body) == 1 and body[0].tag.name == 'p': # special case: "some text {{SomePage}} more text" or "\n{{SomePage}}\n" where SomePage contains a single p. # the content of the transcluded P will be inserted directly into ancestor P. p = body[0] # get attributes from page node; we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"} attrib = Attributes(ret).convert() # make new span node and "convert" p to span by copying all of p's children span = ET.Element(moin_page.span, attrib=attrib, children=p[:]) # insert the new span into the DOM replacing old include, page, body, and p elements elem[i] = span elif not isinstance(body, unicode) and ret.tag.name == 'page' and body.tag.name == 'body': # special case: "some text {{SomePage}} more text" or "\n{{SomePage}}\n" and SomePage body contains multiple p's, a table, preformatted text, etc. # note: ancestor P may have text before or after include if i > 0: # there is text before transclude, make new p node to hold text before include and save in container pa = ET.Element(moin_page.p) pa[:] = elem[0:i] container.append(pa) # get attributes from page node; we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"} attrib = Attributes(ret).convert() # make new div node, copy all of body's children, and save in container div = ET.Element(moin_page.div, attrib=attrib, children=body[:]) container.append(div) # empty elem of siblings that were just placed in container elem[0:i+1] = [] if len(elem) > 0: # there is text after transclude, make new p node to hold text, copy siblings, save in container pa = ET.Element(moin_page.p) pa[:] = elem[:] container.append(pa) elem[:] = [] # elem is now empty so while loop will terminate and container will be returned up one level in recursion else: # ret may be a unicode string: take default action elem[i] = ret else: # default action for any ret not fitting special cases above elem[i] = ret i += 1 if len(container) > 0: return container finally: self.stack.pop()
def test_wikiexternal(conv, input_, output): elem = ET.Element(None) conv.handle_external_links(elem, Iri(input_)) href = elem.get(xlink.href) assert href == output
def inline_url_repl(self, stack, url, url_target): url = Iri(url_target) attrib = {xlink.href: url} element = moin_page.a(attrib=attrib, children=[url_target]) stack.top_append(element)
def __call__(self, content): item_name = content # we just give the name of the item in the content attrib = { xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get'), } return moin_page.a(attrib=attrib, children=["Download %s." % item_name])