A Python parser for MediaWiki wikicode https://mwparserfromhell.readthedocs.io/
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

241 linhas
8.8 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2012 Ben Kurtovic <ben.kurtovic@verizon.net>
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. import re
  23. import sys
  24. from .nodes import Heading, Node, Tag, Template, Text
  25. from .string_mixin import StringMixIn
  26. from .utils import parse_anything
  27. __all__ = ["Wikicode"]
  28. FLAGS = re.IGNORECASE | re.DOTALL | re.UNICODE
  29. class Wikicode(StringMixIn):
  30. def __init__(self, nodes):
  31. super(Wikicode, self).__init__(self)
  32. self._nodes = nodes
  33. def __unicode__(self):
  34. return "".join([unicode(node) for node in self.nodes])
  35. def _get_children(self, node):
  36. for context, child in node.__iternodes__(self._get_all_nodes):
  37. yield child
  38. def _get_context(self, node, obj):
  39. for context, child in node.__iternodes__(self._get_all_nodes):
  40. if child is obj:
  41. return context
  42. raise ValueError(obj)
  43. def _get_all_nodes(self, code):
  44. for node in code.nodes:
  45. for child in self._get_children(node):
  46. yield child
  47. def _is_equivalent(self, obj, node):
  48. if isinstance(obj, Node):
  49. if node is obj:
  50. return True
  51. else:
  52. if node == obj:
  53. return True
  54. return False
  55. def _contains(self, nodes, obj):
  56. if isinstance(obj, Node):
  57. for node in nodes:
  58. if node is obj:
  59. return True
  60. else:
  61. if obj in nodes:
  62. return True
  63. return False
  64. def _do_search(self, obj, recursive, callback, context, *args, **kwargs):
  65. if recursive:
  66. for i, node in enumerate(context.nodes):
  67. if self._is_equivalent(obj, node):
  68. return callback(context, i, *args, **kwargs)
  69. if self._contains(self._get_children(node), obj):
  70. context = self._get_context(node, obj)
  71. return self._do_search(obj, recursive, callback, context,
  72. *args, **kwargs)
  73. raise ValueError(obj)
  74. callback(context, self.index(obj, recursive=False), *args, **kwargs)
  75. def _get_tree(self, code, lines, marker, indent):
  76. def write(*args):
  77. if lines and lines[-1] is marker: # Continue from the last line
  78. lines.pop() # Remove the marker
  79. last = lines.pop()
  80. lines.append(last + " ".join(args))
  81. else:
  82. lines.append(" " * 6 * indent + " ".join(args))
  83. get = lambda code: self._get_tree(code, lines, marker, indent + 1)
  84. mark = lambda: lines.append(marker)
  85. for node in code.nodes:
  86. node.__showtree__(write, get, mark)
  87. return lines
  88. @property
  89. def nodes(self):
  90. return self._nodes
  91. def get(self, index):
  92. return self.nodes[index]
  93. def set(self, index, value):
  94. nodes = parse_anything(value).nodes
  95. if len(nodes) > 1:
  96. raise ValueError("Cannot coerce multiple nodes into one index")
  97. if index >= len(self.nodes) or -1 * index > len(self.nodes):
  98. raise IndexError("List assignment index out of range")
  99. self.nodes.pop(index)
  100. if nodes:
  101. self.nodes[index] = nodes[0]
  102. def index(self, obj, recursive=False):
  103. if recursive:
  104. for i, node in enumerate(self.nodes):
  105. if self._contains(self._get_children(node), obj):
  106. return i
  107. raise ValueError(obj)
  108. for i, node in enumerate(self.nodes):
  109. if self._is_equivalent(obj, node):
  110. return i
  111. raise ValueError(obj)
  112. def insert(self, index, value):
  113. nodes = parse_anything(value).nodes
  114. for node in reversed(nodes):
  115. self.nodes.insert(index, node)
  116. def insert_before(self, obj, value, recursive=True):
  117. callback = lambda self, i, value: self.insert(i, value)
  118. self._do_search(obj, recursive, callback, self, value)
  119. def insert_after(self, obj, value, recursive=True):
  120. callback = lambda self, i, value: self.insert(i + 1, value)
  121. self._do_search(obj, recursive, callback, self, value)
  122. def replace(self, obj, value, recursive=True):
  123. def callback(self, i, value):
  124. self.nodes.pop(i)
  125. self.insert(i, value)
  126. self._do_search(obj, recursive, callback, self, value)
  127. def append(self, value):
  128. nodes = parse_anything(value).nodes
  129. for node in nodes:
  130. self.nodes.append(node)
  131. def remove(self, obj, recursive=True):
  132. callback = lambda self, i: self.nodes.pop(i)
  133. self._do_search(obj, recursive, callback, self)
  134. def ifilter(self, recursive=False, matches=None, flags=FLAGS,
  135. forcetype=None):
  136. if recursive:
  137. nodes = self._get_all_nodes(self)
  138. else:
  139. nodes = self.nodes
  140. for node in nodes:
  141. if not forcetype or isinstance(node, forcetype):
  142. if not matches or re.search(matches, unicode(node), flags):
  143. yield node
  144. def ifilter_templates(self, recursive=False, matches=None, flags=FLAGS):
  145. return self.filter(recursive, matches, flags, forcetype=Template)
  146. def ifilter_text(self, recursive=False, matches=None, flags=FLAGS):
  147. return self.filter(recursive, matches, flags, forcetype=Text)
  148. def ifilter_tags(self, recursive=False, matches=None, flags=FLAGS):
  149. return self.ifilter(recursive, matches, flags, forcetype=Tag)
  150. def filter(self, recursive=False, matches=None, flags=FLAGS,
  151. forcetype=None):
  152. return list(self.ifilter(recursive, matches, flags, forcetype))
  153. def filter_templates(self, recursive=False, matches=None, flags=FLAGS):
  154. return list(self.ifilter_templates(recursive, matches, flags))
  155. def filter_text(self, recursive=False, matches=None, flags=FLAGS):
  156. return list(self.ifilter_text(recursive, matches, flags))
  157. def filter_tags(self, recursive=False, matches=None, flags=FLAGS):
  158. return list(self.ifilter_tags(recursive, matches, flags))
  159. def get_sections(self, flat=True, matches=None, levels=None, flags=FLAGS,
  160. include_headings=True):
  161. if matches:
  162. matches = r"^(=+?)\s*" + matches + r"\s*\1$"
  163. headings = self.filter(recursive=True, matches=matches, flags=flags,
  164. forcetype=Heading)
  165. if levels:
  166. headings = [head for head in headings if head.level in levels]
  167. sections = []
  168. buffers = [[sys.maxint, 0]]
  169. i = 0
  170. while i < len(self.nodes):
  171. if self.nodes[i] in headings:
  172. this = self.nodes[i].level
  173. for (level, start) in buffers:
  174. if not flat or this <= level:
  175. buffers.remove([level, start])
  176. sections.append(self.nodes[start:i])
  177. buffers.append([this, i])
  178. if not include_headings:
  179. i += 1
  180. i += 1
  181. for (level, start) in buffers:
  182. if start != i:
  183. sections.append(self.nodes[start:i])
  184. return sections
  185. def strip_code(self, normalize=True, collapse=True):
  186. nodes = []
  187. for node in self.nodes:
  188. stripped = node.__strip__(normalize, collapse)
  189. if stripped:
  190. nodes.append(unicode(stripped))
  191. if collapse:
  192. stripped = u"".join(nodes).strip("\n")
  193. while "\n\n\n" in stripped:
  194. stripped = stripped.replace("\n\n\n", "\n\n")
  195. return stripped
  196. else:
  197. return u"".join(nodes)
  198. def get_tree(self):
  199. marker = object() # Random object we can find with certainty in a list
  200. return "\n".join(self._get_tree(self, [], marker, 0))