A Python parser for MediaWiki wikicode https://mwparserfromhell.readthedocs.io/
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

243 satır
8.9 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 mwparserfromhell
  24. from mwparserfromhell.nodes import HTMLEntity, Node, Template, Text
  25. from mwparserfromhell.string_mixin import StringMixIn
  26. __all__ = ["Wikicode"]
  27. FLAGS = re.I | re.S | re.U
  28. class Wikicode(StringMixIn):
  29. def __init__(self, nodes):
  30. self._nodes = nodes
  31. def __unicode__(self):
  32. return "".join([unicode(node) for node in self.nodes])
  33. def _nodify(self, value):
  34. if isinstance(value, Wikicode):
  35. return value.nodes
  36. if isinstance(value, Node):
  37. return [value]
  38. if isinstance(value, basestring):
  39. return mwparserfromhell.parse(value).nodes
  40. try:
  41. nodelist = list(value)
  42. except TypeError:
  43. error = "Needs string, Node, iterable of Nodes, or Wikicode object, but got {0}: {1}"
  44. raise ValueError(error.format(type(value), value))
  45. if not all([isinstance(node, Node) for node in nodelist]):
  46. error = "Was passed an interable {0}, but it did not contain all Nodes: {1}"
  47. raise ValueError(error.format(type(value), value))
  48. return nodelist
  49. def _iterate_over_children(self, node):
  50. yield (None, node)
  51. if isinstance(node, Template):
  52. for child in self._get_all_nodes(node.name):
  53. yield (node.name, child)
  54. for param in node.params:
  55. if param.showkey:
  56. for child in self._get_all_nodes(param.name):
  57. yield (param.name, child)
  58. for child in self._get_all_nodes(param.value):
  59. yield (param.value, child)
  60. def _get_children(self, node):
  61. for context, child in self._iterate_over_children(node):
  62. yield child
  63. def _get_context(self, node, obj):
  64. for context, child in self._iterate_over_children(node):
  65. if child is obj:
  66. return context
  67. raise ValueError(obj)
  68. def _get_all_nodes(self, code):
  69. for node in code.nodes:
  70. for child in self._get_children(node):
  71. yield child
  72. def _is_equivalent(self, obj, node):
  73. if isinstance(obj, Node):
  74. if node is obj:
  75. return True
  76. else:
  77. if node == obj:
  78. return True
  79. return False
  80. def _contains(self, nodes, obj):
  81. if isinstance(obj, Node):
  82. for node in nodes:
  83. if node is obj:
  84. return True
  85. else:
  86. if obj in nodes:
  87. return True
  88. return False
  89. def _do_search(self, obj, recursive, callback, context, *args, **kwargs):
  90. if recursive:
  91. for i, node in enumerate(context.nodes):
  92. if self._is_equivalent(obj, node):
  93. return callback(context, i, *args, **kwargs)
  94. if self._contains(self._get_children(node), obj):
  95. context = self._get_context(node, obj)
  96. return self._do_search(obj, recursive, callback, context,
  97. *args, **kwargs)
  98. raise ValueError(obj)
  99. callback(context, self.index(obj, recursive=False), *args, **kwargs)
  100. def _get_tree(self, code, lines, marker=None, indent=0):
  101. def write(*args):
  102. if lines and lines[-1] is marker: # Continue from the last line
  103. lines.pop() # Remove the marker
  104. last = lines.pop()
  105. lines.append(last + " ".join(args))
  106. else:
  107. lines.append(" " * indent + " ".join(args))
  108. for node in code.nodes:
  109. if isinstance(node, Template):
  110. write("{{", )
  111. self._get_tree(node.name, lines, marker, indent + 1)
  112. for param in node.params:
  113. write(" | ")
  114. lines.append(marker) # Continue from this line
  115. self._get_tree(param.name, lines, marker, indent + 1)
  116. write(" = ")
  117. lines.append(marker) # Continue from this line
  118. self._get_tree(param.value, lines, marker, indent + 1)
  119. write("}}")
  120. else:
  121. write(unicode(node))
  122. return lines
  123. @property
  124. def nodes(self):
  125. return self._nodes
  126. def get(self, index):
  127. return self.nodes[index]
  128. def set(self, index, value):
  129. nodes = self._nodify(value)
  130. if len(nodes) > 1:
  131. raise ValueError("Cannot coerce multiple nodes into one index")
  132. if index >= len(self.nodes) or -1 * index > len(self.nodes):
  133. raise IndexError("List assignment index out of range")
  134. self.nodex.pop(index)
  135. if nodes:
  136. self.nodes[index] = nodes[0]
  137. def index(self, obj, recursive=False):
  138. if recursive:
  139. for i, node in enumerate(self.nodes):
  140. if self._contains(self._get_children(node), obj):
  141. return i
  142. raise ValueError(obj)
  143. for i, node in enumerate(self.nodes):
  144. if self._is_equivalent(obj, node):
  145. return i
  146. raise ValueError(obj)
  147. def insert(self, index, value):
  148. nodes = self._nodify(value)
  149. for node in reversed(nodes):
  150. self.nodes.insert(index, node)
  151. def insert_before(self, obj, value, recursive=True):
  152. callback = lambda self, i, value: self.insert(i, value)
  153. self._do_search(obj, recursive, callback, self, value)
  154. def insert_after(self, obj, value, recursive=True):
  155. callback = lambda self, i, value: self.insert(i + 1, value)
  156. self._do_search(obj, recursive, callback, self, value)
  157. def replace(self, obj, value, recursive=True):
  158. def callback(self, i, value):
  159. self.nodes.pop(i)
  160. self.insert(i, value)
  161. self._do_search(obj, recursive, callback, self, value)
  162. def append(self, value):
  163. nodes = self._nodify(value)
  164. for node in nodes:
  165. self.nodes.append(node)
  166. def remove(self, obj, recursive=True):
  167. callback = lambda self, i: self.nodes.pop(i)
  168. self._do_search(obj, recursive, callback, self)
  169. def ifilter(self, recursive=False, matches=None, flags=FLAGS,
  170. forcetype=None):
  171. if recursive:
  172. nodes = self._get_all_nodes(self)
  173. else:
  174. nodes = self.nodes
  175. for node in nodes:
  176. if not forcetype or isinstance(node, forcetype):
  177. if not matches or re.search(matches, unicode(node), flags):
  178. yield node
  179. def ifilter_templates(self, recursive=False, matches=None, flags=FLAGS):
  180. return self.filter(recursive, matches, flags, forcetype=Template)
  181. def ifilter_text(self, recursive=False, matches=None, flags=FLAGS):
  182. return self.filter(recursive, matches, flags, forcetype=Text)
  183. def filter(self, recursive=False, matches=None, flags=FLAGS,
  184. forcetype=None):
  185. return list(self.ifilter(recursive, matches, flags, forcetype))
  186. def filter_templates(self, recursive=False, matches=None, flags=FLAGS):
  187. return list(self.ifilter_templates(recursive, matches, flags))
  188. def filter_text(self, recursive=False, matches=None, flags=FLAGS):
  189. return list(self.ifilter_text(recursive, matches, flags))
  190. def strip_code(self, normalize=True):
  191. nodes = []
  192. for node in self.nodes:
  193. if isinstance(node, Text):
  194. nodes.append(node)
  195. elif isinstance(node, HTMLEntity):
  196. if normalize:
  197. nodes.append(node.normalize())
  198. else:
  199. nodes.append(node)
  200. return u" ".join(nodes)
  201. def get_tree(self):
  202. marker = object() # Random object we can find with certainty in a list
  203. return "\n".join(self._get_tree(self, [], marker))