A Python parser for MediaWiki wikicode https://mwparserfromhell.readthedocs.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

192 lines
7.0 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.node import Node
  25. from mwparserfromhell.string_mixin import StringMixIn
  26. from mwparserfromhell.template import Template
  27. from mwparserfromhell.text import Text
  28. __all__ = ["Wikicode"]
  29. FLAGS = re.I | re.S | re.U
  30. class Wikicode(StringMixIn):
  31. def __init__(self, nodes):
  32. self._nodes = nodes
  33. def __unicode__(self):
  34. return "".join([unicode(node) for node in self.nodes])
  35. def _nodify(self, value):
  36. if isinstance(value, Wikicode):
  37. return value.nodes
  38. if isinstance(value, Node):
  39. return [value]
  40. if isinstance(value, str) or isinstance(value, unicode):
  41. return mwparserfromhell.parse(value).nodes
  42. error = "Needs string, Node, or Wikicode object, but got {0}: {1}"
  43. raise ValueError(error.format(type(value), value))
  44. def _get_children(self, node):
  45. yield node
  46. if isinstance(node, Template):
  47. for child in self._get_all_nodes(node.name):
  48. yield child
  49. for param in node.params:
  50. if param.showkey:
  51. for child in self._get_all_nodes(param.name):
  52. yield child
  53. for child in self._get_all_nodes(param.value):
  54. yield child
  55. def _get_all_nodes(self, code):
  56. for node in code.nodes:
  57. for child in self._get_children(node):
  58. yield child
  59. def _do_recursive_index(self, obj):
  60. for i, node in enumerate(self.nodes):
  61. children = self._get_children(node)
  62. if isinstance(obj, Node):
  63. for child in children:
  64. if child is obj:
  65. return i
  66. else:
  67. if obj in children:
  68. return i
  69. raise ValueError(obj)
  70. def _show_tree(self, code, lines, marker=None, indent=0):
  71. def write(*args):
  72. if lines and lines[-1] is marker: # Continue from the last line
  73. lines.pop() # Remove the marker
  74. last = lines.pop()
  75. lines.append(last + " ".join(args))
  76. else:
  77. lines.append(" " * indent + " ".join(args))
  78. for node in code.nodes:
  79. if isinstance(node, Template):
  80. write("{{", )
  81. self._show_tree(node.name, lines, marker, indent + 1)
  82. for param in node.params:
  83. write(" | ")
  84. lines.append(marker) # Continue from this line
  85. self._show_tree(param.name, lines, marker, indent + 1)
  86. write(" = ")
  87. lines.append(marker) # Continue from this line
  88. self._show_tree(param.value, lines, marker, indent + 1)
  89. write("}}")
  90. elif isinstance(node, Text):
  91. write(unicode(node))
  92. else:
  93. raise NotImplementedError(node)
  94. return lines
  95. @property
  96. def nodes(self):
  97. return self._nodes
  98. def get(self, index):
  99. return self.nodes[index]
  100. def set(self, index, value):
  101. nodes = self._nodify(value)
  102. if len(nodes) > 1:
  103. raise ValueError("Cannot coerce multiple nodes into one index")
  104. if index >= len(self.nodes) or -1 * index > len(self.nodes):
  105. raise IndexError("List assignment index out of range")
  106. self.nodex.pop(index)
  107. if nodes:
  108. self.nodes[index] = nodes[0]
  109. def index(self, obj, recursive=False):
  110. if recursive:
  111. return self._do_recursive_index()
  112. if isinstance(obj, Node):
  113. for i, node in enumerate(self.nodes):
  114. if node is obj:
  115. return i
  116. raise ValueError(obj)
  117. return self.nodes.index(obj)
  118. def insert(self, index, value):
  119. nodes = self._nodify(value)
  120. for node in reversed(nodes):
  121. self.nodes.insert(index, node)
  122. def insert_before(self, obj, value, recursive=True):
  123. if obj not in self.nodes:
  124. raise KeyError(obj)
  125. self.insert(self.index(obj), value)
  126. def insert_after(self, obj, value, recursive=True):
  127. if obj not in self.nodes:
  128. raise KeyError(obj)
  129. self.insert(self.index(obj) + 1, value)
  130. def append(self, value):
  131. nodes = self._nodify(value)
  132. for node in nodes:
  133. self.nodes.append(node)
  134. def remove(self, node, recursive=True):
  135. self.nodes.pop(self.index(node))
  136. def ifilter(self, recursive=False, matches=None, flags=FLAGS,
  137. forcetype=None):
  138. if recursive:
  139. nodes = self._get_all_nodes(self)
  140. else:
  141. nodes = self.nodes
  142. for node in nodes:
  143. if not forcetype or isinstance(node, forcetype):
  144. if not matches or re.search(matches, unicode(node), flags):
  145. yield node
  146. def ifilter_templates(self, recursive=False, matches=None, flags=FLAGS):
  147. return self.filter(recursive, matches, flags, forcetype=Template)
  148. def ifilter_text(self, recursive=False, matches=None, flags=FLAGS):
  149. return self.filter(recursive, matches, flags, forcetype=Text)
  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 normalize(self):
  158. ## Create a deep copy of self ##
  159. return normalized
  160. def show_tree(self):
  161. marker = object() # Random object we can find with certainty in a list
  162. print "\n".join(self._show_tree(self, [], marker))