brakeman 4.10.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +46 -0
  3. data/README.md +11 -2
  4. data/bundle/load.rb +5 -3
  5. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/CHANGELOG.md +16 -0
  6. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/FAQ.md +0 -0
  7. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/Gemfile +1 -4
  8. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/MIT-LICENSE +0 -0
  9. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/README.md +2 -3
  10. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/REFERENCE.md +29 -7
  11. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/TODO +0 -0
  12. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/haml.gemspec +2 -1
  13. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml.rb +0 -0
  14. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/attribute_builder.rb +3 -3
  15. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/attribute_compiler.rb +42 -31
  16. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/attribute_parser.rb +0 -0
  17. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/buffer.rb +0 -0
  18. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/compiler.rb +0 -0
  19. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/engine.rb +0 -0
  20. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/error.rb +0 -0
  21. data/bundle/ruby/2.7.0/gems/haml-5.2.1/lib/haml/escapable.rb +77 -0
  22. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/exec.rb +0 -0
  23. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/filters.rb +0 -0
  24. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/generator.rb +0 -0
  25. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers.rb +7 -1
  26. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/action_view_extensions.rb +0 -0
  27. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/action_view_mods.rb +0 -0
  28. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/action_view_xss_mods.rb +0 -0
  29. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/safe_erubi_template.rb +0 -0
  30. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/safe_erubis_template.rb +0 -0
  31. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/xss_mods.rb +6 -3
  32. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/options.rb +0 -0
  33. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/parser.rb +32 -4
  34. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/plugin.rb +0 -0
  35. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/railtie.rb +0 -0
  36. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/sass_rails_filter.rb +0 -0
  37. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/template.rb +0 -0
  38. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/template/options.rb +0 -0
  39. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/temple_engine.rb +0 -0
  40. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/temple_line_counter.rb +0 -0
  41. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/util.rb +1 -1
  42. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/version.rb +1 -1
  43. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/yard/default/fulldoc/html/css/common.sass +0 -0
  44. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/yard/default/layout/html/footer.erb +0 -0
  45. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/MIT-LICENSE.txt +20 -0
  46. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel.rb +523 -0
  47. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/processor_count.rb +42 -0
  48. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/version.rb +3 -0
  49. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/LICENSE.txt +22 -0
  50. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/NEWS.md +178 -0
  51. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/README.md +48 -0
  52. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml.rb +3 -0
  53. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/attlistdecl.rb +63 -0
  54. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/attribute.rb +205 -0
  55. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/cdata.rb +68 -0
  56. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/child.rb +97 -0
  57. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/comment.rb +80 -0
  58. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/doctype.rb +311 -0
  59. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/document.rb +451 -0
  60. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/attlistdecl.rb +11 -0
  61. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/dtd.rb +47 -0
  62. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/elementdecl.rb +18 -0
  63. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/entitydecl.rb +57 -0
  64. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/notationdecl.rb +40 -0
  65. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/element.rb +2599 -0
  66. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/encoding.rb +51 -0
  67. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/entity.rb +171 -0
  68. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/formatters/default.rb +116 -0
  69. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/formatters/pretty.rb +142 -0
  70. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/formatters/transitive.rb +58 -0
  71. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/functions.rb +447 -0
  72. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/instruction.rb +79 -0
  73. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/light/node.rb +188 -0
  74. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/namespace.rb +59 -0
  75. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/node.rb +76 -0
  76. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/output.rb +30 -0
  77. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parent.rb +166 -0
  78. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parseexception.rb +52 -0
  79. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/baseparser.rb +694 -0
  80. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/lightparser.rb +59 -0
  81. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/pullparser.rb +197 -0
  82. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/sax2parser.rb +273 -0
  83. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/streamparser.rb +61 -0
  84. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/treeparser.rb +101 -0
  85. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/ultralightparser.rb +57 -0
  86. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/xpathparser.rb +689 -0
  87. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/quickpath.rb +266 -0
  88. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/rexml.rb +37 -0
  89. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/sax2listener.rb +98 -0
  90. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/security.rb +28 -0
  91. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/source.rb +298 -0
  92. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/streamlistener.rb +93 -0
  93. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/text.rb +424 -0
  94. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/undefinednamespaceexception.rb +9 -0
  95. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/validation/relaxng.rb +539 -0
  96. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/validation/validation.rb +144 -0
  97. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/validation/validationexception.rb +10 -0
  98. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xmldecl.rb +130 -0
  99. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xmltokens.rb +85 -0
  100. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xpath.rb +81 -0
  101. data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xpath_parser.rb +974 -0
  102. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/History.rdoc +25 -0
  103. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/Manifest.txt +2 -0
  104. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/README.rdoc +0 -0
  105. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/compare/normalize.rb +2 -2
  106. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/debugging.md +190 -0
  107. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/rp_extensions.rb +0 -0
  108. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/rp_stringscanner.rb +0 -0
  109. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby20_parser.rb +2392 -2384
  110. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby20_parser.y +6 -1
  111. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby21_parser.rb +2553 -2550
  112. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby21_parser.y +6 -1
  113. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby22_parser.rb +2491 -2471
  114. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby22_parser.y +6 -1
  115. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby23_parser.rb +2422 -2403
  116. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby23_parser.y +6 -1
  117. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby24_parser.rb +2460 -2450
  118. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby24_parser.y +6 -1
  119. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby25_parser.rb +2450 -2441
  120. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby25_parser.y +6 -1
  121. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby26_parser.rb +2444 -2433
  122. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby26_parser.y +7 -1
  123. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby27_parser.rb +7310 -0
  124. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby27_parser.y +21 -1
  125. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.rb +7310 -0
  126. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.y +2677 -0
  127. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_lexer.rb +19 -0
  128. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_lexer.rex +1 -1
  129. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_lexer.rex.rb +1 -1
  130. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_parser.rb +2 -0
  131. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_parser.yy +27 -1
  132. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_parser_extras.rb +2 -2
  133. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/tools/munge.rb +2 -2
  134. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/tools/ripper.rb +0 -0
  135. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/History.rdoc +12 -0
  136. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/Manifest.txt +0 -0
  137. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/README.rdoc +0 -0
  138. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/composite_sexp_processor.rb +0 -0
  139. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/pt_testcase.rb +2 -2
  140. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/sexp.rb +0 -0
  141. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/sexp_matcher.rb +0 -0
  142. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/sexp_processor.rb +1 -1
  143. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/strict_sexp.rb +0 -0
  144. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/unique.rb +0 -0
  145. data/lib/brakeman.rb +21 -4
  146. data/lib/brakeman/app_tree.rb +36 -3
  147. data/lib/brakeman/checks/base_check.rb +7 -1
  148. data/lib/brakeman/checks/check_detailed_exceptions.rb +1 -1
  149. data/lib/brakeman/checks/check_evaluation.rb +1 -1
  150. data/lib/brakeman/checks/check_execute.rb +2 -1
  151. data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
  152. data/lib/brakeman/checks/check_regex_dos.rb +1 -1
  153. data/lib/brakeman/checks/check_sanitize_methods.rb +2 -1
  154. data/lib/brakeman/checks/check_sql.rb +16 -3
  155. data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
  156. data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
  157. data/lib/brakeman/file_parser.rb +50 -22
  158. data/lib/brakeman/options.rb +5 -1
  159. data/lib/brakeman/parsers/template_parser.rb +26 -3
  160. data/lib/brakeman/processors/alias_processor.rb +91 -19
  161. data/lib/brakeman/processors/base_processor.rb +4 -4
  162. data/lib/brakeman/processors/controller_alias_processor.rb +6 -43
  163. data/lib/brakeman/processors/controller_processor.rb +1 -1
  164. data/lib/brakeman/processors/haml_template_processor.rb +8 -1
  165. data/lib/brakeman/processors/lib/call_conversion_helper.rb +10 -0
  166. data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
  167. data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
  168. data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
  169. data/lib/brakeman/processors/library_processor.rb +9 -0
  170. data/lib/brakeman/processors/output_processor.rb +1 -1
  171. data/lib/brakeman/processors/template_alias_processor.rb +5 -0
  172. data/lib/brakeman/report.rb +12 -1
  173. data/lib/brakeman/report/ignore/interactive.rb +1 -1
  174. data/lib/brakeman/report/report_base.rb +0 -2
  175. data/lib/brakeman/report/report_csv.rb +37 -60
  176. data/lib/brakeman/report/report_github.rb +31 -0
  177. data/lib/brakeman/report/report_junit.rb +2 -2
  178. data/lib/brakeman/report/report_sarif.rb +1 -1
  179. data/lib/brakeman/report/report_sonar.rb +38 -0
  180. data/lib/brakeman/report/report_tabs.rb +1 -1
  181. data/lib/brakeman/report/report_text.rb +1 -1
  182. data/lib/brakeman/rescanner.rb +7 -5
  183. data/lib/brakeman/scanner.rb +47 -18
  184. data/lib/brakeman/tracker.rb +39 -4
  185. data/lib/brakeman/tracker/collection.rb +27 -5
  186. data/lib/brakeman/tracker/config.rb +73 -0
  187. data/lib/brakeman/tracker/controller.rb +1 -1
  188. data/lib/brakeman/tracker/method_info.rb +29 -0
  189. data/lib/brakeman/util.rb +17 -4
  190. data/lib/brakeman/version.rb +1 -1
  191. data/lib/brakeman/warning.rb +10 -2
  192. data/lib/brakeman/warning_codes.rb +2 -0
  193. data/lib/ruby_parser/bm_sexp.rb +9 -9
  194. metadata +149 -84
  195. data/bundle/ruby/2.7.0/gems/haml-5.1.2/lib/haml/escapable.rb +0 -50
  196. data/bundle/ruby/2.7.0/gems/ruby_parser-3.15.0/debugging.md +0 -57
  197. data/bundle/ruby/2.7.0/gems/ruby_parser-3.15.0/lib/ruby27_parser.rb +0 -7224
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: false
2
+ require_relative "elementdecl"
3
+ require_relative "entitydecl"
4
+ require_relative "../comment"
5
+ require_relative "notationdecl"
6
+ require_relative "attlistdecl"
7
+ require_relative "../parent"
8
+
9
+ module REXML
10
+ module DTD
11
+ class Parser
12
+ def Parser.parse( input )
13
+ case input
14
+ when String
15
+ parse_helper input
16
+ when File
17
+ parse_helper input.read
18
+ end
19
+ end
20
+
21
+ # Takes a String and parses it out
22
+ def Parser.parse_helper( input )
23
+ contents = Parent.new
24
+ while input.size > 0
25
+ case input
26
+ when ElementDecl.PATTERN_RE
27
+ match = $&
28
+ contents << ElementDecl.new( match )
29
+ when AttlistDecl.PATTERN_RE
30
+ matchdata = $~
31
+ contents << AttlistDecl.new( matchdata )
32
+ when EntityDecl.PATTERN_RE
33
+ matchdata = $~
34
+ contents << EntityDecl.new( matchdata )
35
+ when Comment.PATTERN_RE
36
+ matchdata = $~
37
+ contents << Comment.new( matchdata )
38
+ when NotationDecl.PATTERN_RE
39
+ matchdata = $~
40
+ contents << NotationDecl.new( matchdata )
41
+ end
42
+ end
43
+ contents
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: false
2
+ require_relative "../child"
3
+ module REXML
4
+ module DTD
5
+ class ElementDecl < Child
6
+ START = "<!ELEMENT"
7
+ START_RE = /^\s*#{START}/um
8
+ # PATTERN_RE = /^\s*(#{START}.*?)>/um
9
+ PATTERN_RE = /^\s*#{START}\s+((?:[:\w][-\.\w]*:)?[-!\*\.\w]*)(.*?)>/
10
+ #\s*((((["']).*?\5)|[^\/'">]*)*?)(\/)?>/um, true)
11
+
12
+ def initialize match
13
+ @name = match[1]
14
+ @rest = match[2]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: false
2
+ require_relative "../child"
3
+ module REXML
4
+ module DTD
5
+ class EntityDecl < Child
6
+ START = "<!ENTITY"
7
+ START_RE = /^\s*#{START}/um
8
+ PUBLIC = /^\s*#{START}\s+(?:%\s+)?(\w+)\s+PUBLIC\s+((["']).*?\3)\s+((["']).*?\5)\s*>/um
9
+ SYSTEM = /^\s*#{START}\s+(?:%\s+)?(\w+)\s+SYSTEM\s+((["']).*?\3)(?:\s+NDATA\s+\w+)?\s*>/um
10
+ PLAIN = /^\s*#{START}\s+(\w+)\s+((["']).*?\3)\s*>/um
11
+ PERCENT = /^\s*#{START}\s+%\s+(\w+)\s+((["']).*?\3)\s*>/um
12
+ # <!ENTITY name SYSTEM "...">
13
+ # <!ENTITY name "...">
14
+ def initialize src
15
+ super()
16
+ md = nil
17
+ if src.match( PUBLIC )
18
+ md = src.match( PUBLIC, true )
19
+ @middle = "PUBLIC"
20
+ @content = "#{md[2]} #{md[4]}"
21
+ elsif src.match( SYSTEM )
22
+ md = src.match( SYSTEM, true )
23
+ @middle = "SYSTEM"
24
+ @content = md[2]
25
+ elsif src.match( PLAIN )
26
+ md = src.match( PLAIN, true )
27
+ @middle = ""
28
+ @content = md[2]
29
+ elsif src.match( PERCENT )
30
+ md = src.match( PERCENT, true )
31
+ @middle = ""
32
+ @content = md[2]
33
+ end
34
+ raise ParseException.new("failed Entity match", src) if md.nil?
35
+ @name = md[1]
36
+ end
37
+
38
+ def to_s
39
+ rv = "<!ENTITY #@name "
40
+ rv << "#@middle " if @middle.size > 0
41
+ rv << @content
42
+ rv
43
+ end
44
+
45
+ def write( output, indent )
46
+ indent( output, indent )
47
+ output << to_s
48
+ end
49
+
50
+ def EntityDecl.parse_source source, listener
51
+ md = source.match( PATTERN_RE, true )
52
+ thing = md[0].squeeze(" \t\n\r")
53
+ listener.send inspect.downcase, thing
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: false
2
+ require_relative "../child"
3
+ module REXML
4
+ module DTD
5
+ class NotationDecl < Child
6
+ START = "<!NOTATION"
7
+ START_RE = /^\s*#{START}/um
8
+ PUBLIC = /^\s*#{START}\s+(\w[\w-]*)\s+(PUBLIC)\s+((["']).*?\4)\s*>/um
9
+ SYSTEM = /^\s*#{START}\s+(\w[\w-]*)\s+(SYSTEM)\s+((["']).*?\4)\s*>/um
10
+ def initialize src
11
+ super()
12
+ if src.match( PUBLIC )
13
+ md = src.match( PUBLIC, true )
14
+ elsif src.match( SYSTEM )
15
+ md = src.match( SYSTEM, true )
16
+ else
17
+ raise ParseException.new( "error parsing notation: no matching pattern", src )
18
+ end
19
+ @name = md[1]
20
+ @middle = md[2]
21
+ @rest = md[3]
22
+ end
23
+
24
+ def to_s
25
+ "<!NOTATION #@name #@middle #@rest>"
26
+ end
27
+
28
+ def write( output, indent )
29
+ indent( output, indent )
30
+ output << to_s
31
+ end
32
+
33
+ def NotationDecl.parse_source source, listener
34
+ md = source.match( PATTERN_RE, true )
35
+ thing = md[0].squeeze(" \t\n\r")
36
+ listener.send inspect.downcase, thing
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,2599 @@
1
+ # frozen_string_literal: false
2
+ require_relative "parent"
3
+ require_relative "namespace"
4
+ require_relative "attribute"
5
+ require_relative "cdata"
6
+ require_relative "xpath"
7
+ require_relative "parseexception"
8
+
9
+ module REXML
10
+ # An implementation note about namespaces:
11
+ # As we parse, when we find namespaces we put them in a hash and assign
12
+ # them a unique ID. We then convert the namespace prefix for the node
13
+ # to the unique ID. This makes namespace lookup much faster for the
14
+ # cost of extra memory use. We save the namespace prefix for the
15
+ # context node and convert it back when we write it.
16
+ @@namespaces = {}
17
+
18
+ # An \REXML::Element object represents an XML element.
19
+ #
20
+ # An element:
21
+ #
22
+ # - Has a name (string).
23
+ # - May have a parent (another element).
24
+ # - Has zero or more children
25
+ # (other elements, text, CDATA, processing instructions, and comments).
26
+ # - Has zero or more siblings
27
+ # (other elements, text, CDATA, processing instructions, and comments).
28
+ # - Has zero or more named attributes.
29
+ #
30
+ # == In a Hurry?
31
+ #
32
+ # If you're somewhat familiar with XML
33
+ # and have a particular task in mind,
34
+ # you may want to see the
35
+ # {tasks pages}[../doc/rexml/tasks/tocs/master_toc_rdoc.html],
36
+ # and in particular, the
37
+ # {tasks page for elements}[../doc/rexml/tasks/tocs/element_toc_rdoc.html].
38
+ #
39
+ # === Name
40
+ #
41
+ # An element has a name, which is initially set when the element is created:
42
+ #
43
+ # e = REXML::Element.new('foo')
44
+ # e.name # => "foo"
45
+ #
46
+ # The name may be changed:
47
+ #
48
+ # e.name = 'bar'
49
+ # e.name # => "bar"
50
+ #
51
+ #
52
+ # === \Parent
53
+ #
54
+ # An element may have a parent.
55
+ #
56
+ # Its parent may be assigned explicitly when the element is created:
57
+ #
58
+ # e0 = REXML::Element.new('foo')
59
+ # e1 = REXML::Element.new('bar', e0)
60
+ # e1.parent # => <foo> ... </>
61
+ #
62
+ # Note: the representation of an element always shows the element's name.
63
+ # If the element has children, the representation indicates that
64
+ # by including an ellipsis (<tt>...</tt>).
65
+ #
66
+ # The parent may be assigned explicitly at any time:
67
+ #
68
+ # e2 = REXML::Element.new('baz')
69
+ # e1.parent = e2
70
+ # e1.parent # => <baz/>
71
+ #
72
+ # When an element is added as a child, its parent is set automatically:
73
+ #
74
+ # e1.add_element(e0)
75
+ # e0.parent # => <bar> ... </>
76
+ #
77
+ # For an element that has no parent, method +parent+ returns +nil+.
78
+ #
79
+ # === Children
80
+ #
81
+ # An element has zero or more children.
82
+ # The children are an ordered collection
83
+ # of all objects whose parent is the element itself.
84
+ #
85
+ # The children may include any combination of elements, text, comments,
86
+ # processing instructions, and CDATA.
87
+ # (This example keeps things clean by controlling whitespace
88
+ # via a +context+ setting.)
89
+ #
90
+ # xml_string = <<-EOT
91
+ # <root>
92
+ # <ele_0/>
93
+ # text 0
94
+ # <!--comment 0-->
95
+ # <?target_0 pi_0?>
96
+ # <![CDATA[cdata 0]]>
97
+ # <ele_1/>
98
+ # text 1
99
+ # <!--comment 1-->
100
+ # <?target_0 pi_1?>
101
+ # <![CDATA[cdata 1]]>
102
+ # </root>
103
+ # EOT
104
+ # context = {ignore_whitespace_nodes: :all, compress_whitespace: :all}
105
+ # d = REXML::Document.new(xml_string, context)
106
+ # root = d.root
107
+ # root.children.size # => 10
108
+ # root.each {|child| p "#{child.class}: #{child}" }
109
+ #
110
+ # Output:
111
+ #
112
+ # "REXML::Element: <ele_0/>"
113
+ # "REXML::Text: \n text 0\n "
114
+ # "REXML::Comment: comment 0"
115
+ # "REXML::Instruction: <?target_0 pi_0?>"
116
+ # "REXML::CData: cdata 0"
117
+ # "REXML::Element: <ele_1/>"
118
+ # "REXML::Text: \n text 1\n "
119
+ # "REXML::Comment: comment 1"
120
+ # "REXML::Instruction: <?target_0 pi_1?>"
121
+ # "REXML::CData: cdata 1"
122
+ #
123
+ # A child may be added using inherited methods
124
+ # Parent#insert_before or Parent#insert_after:
125
+ #
126
+ # xml_string = '<root><a/><c/><d/></root>'
127
+ # d = REXML::Document.new(xml_string)
128
+ # root = d.root
129
+ # c = d.root[1] # => <c/>
130
+ # root.insert_before(c, REXML::Element.new('b'))
131
+ # root.to_a # => [<a/>, <b/>, <c/>, <d/>]
132
+ #
133
+ # A child may be replaced using Parent#replace_child:
134
+ #
135
+ # root.replace_child(c, REXML::Element.new('x'))
136
+ # root.to_a # => [<a/>, <b/>, <x/>, <d/>]
137
+ #
138
+ # A child may be removed using Parent#delete:
139
+ #
140
+ # x = root[2] # => <x/>
141
+ # root.delete(x)
142
+ # root.to_a # => [<a/>, <b/>, <d/>]
143
+ #
144
+ # === Siblings
145
+ #
146
+ # An element has zero or more siblings,
147
+ # which are the other children of the element's parent.
148
+ #
149
+ # In the example above, element +ele_1+ is between a CDATA sibling
150
+ # and a text sibling:
151
+ #
152
+ # ele_1 = root[5] # => <ele_1/>
153
+ # ele_1.previous_sibling # => "cdata 0"
154
+ # ele_1.next_sibling # => "\n text 1\n "
155
+ #
156
+ # === \Attributes
157
+ #
158
+ # An element has zero or more named attributes.
159
+ #
160
+ # A new element has no attributes:
161
+ #
162
+ # e = REXML::Element.new('foo')
163
+ # e.attributes # => {}
164
+ #
165
+ # Attributes may be added:
166
+ #
167
+ # e.add_attribute('bar', 'baz')
168
+ # e.add_attribute('bat', 'bam')
169
+ # e.attributes.size # => 2
170
+ # e['bar'] # => "baz"
171
+ # e['bat'] # => "bam"
172
+ #
173
+ # An existing attribute may be modified:
174
+ #
175
+ # e.add_attribute('bar', 'bad')
176
+ # e.attributes.size # => 2
177
+ # e['bar'] # => "bad"
178
+ #
179
+ # An existing attribute may be deleted:
180
+ #
181
+ # e.delete_attribute('bar')
182
+ # e.attributes.size # => 1
183
+ # e['bar'] # => nil
184
+ #
185
+ # == What's Here
186
+ #
187
+ # To begin with, what's elsewhere?
188
+ #
189
+ # \Class \REXML::Element inherits from its ancestor classes:
190
+ #
191
+ # - REXML::Child
192
+ # - REXML::Parent
193
+ #
194
+ # \REXML::Element itself and its ancestors also include modules:
195
+ #
196
+ # - {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html]
197
+ # - REXML::Namespace
198
+ # - REXML::Node
199
+ # - REXML::XMLTokens
200
+ #
201
+ # === Methods for Creating an \Element
202
+ #
203
+ # ::new:: Returns a new empty element.
204
+ # #clone:: Returns a clone of another element.
205
+ #
206
+ # === Methods for Attributes
207
+ #
208
+ # {[attribute_name]}[#method-i-5B-5D]:: Returns an attribute value.
209
+ # #add_attribute:: Adds a new attribute.
210
+ # #add_attributes:: Adds multiple new attributes.
211
+ # #attribute:: Returns the attribute value for a given name and optional namespace.
212
+ # #delete_attribute:: Removes an attribute.
213
+ #
214
+ # === Methods for Children
215
+ #
216
+ # {[index]}[#method-i-5B-5D]:: Returns the child at the given offset.
217
+ # #add_element:: Adds an element as the last child.
218
+ # #delete_element:: Deletes a child element.
219
+ # #each_element:: Calls the given block with each child element.
220
+ # #each_element_with_attribute:: Calls the given block with each child element
221
+ # that meets given criteria,
222
+ # which can include the attribute name.
223
+ # #each_element_with_text:: Calls the given block with each child element
224
+ # that meets given criteria,
225
+ # which can include text.
226
+ # #get_elements:: Returns an array of element children that match a given xpath.
227
+ #
228
+ # === Methods for \Text Children
229
+ #
230
+ # #add_text:: Adds a text node to the element.
231
+ # #get_text:: Returns a text node that meets specified criteria.
232
+ # #text:: Returns the text string from the first node that meets specified criteria.
233
+ # #texts:: Returns an array of the text children of the element.
234
+ # #text=:: Adds, removes, or replaces the first text child of the element
235
+ #
236
+ # === Methods for Other Children
237
+ #
238
+ # #cdatas:: Returns an array of the cdata children of the element.
239
+ # #comments:: Returns an array of the comment children of the element.
240
+ # #instructions:: Returns an array of the instruction children of the element.
241
+ #
242
+ # === Methods for Namespaces
243
+ #
244
+ # #add_namespace:: Adds a namespace to the element.
245
+ # #delete_namespace:: Removes a namespace from the element.
246
+ # #namespace:: Returns the string namespace URI for the element.
247
+ # #namespaces:: Returns a hash of all defined namespaces in the element.
248
+ # #prefixes:: Returns an array of the string prefixes (names)
249
+ # of all defined namespaces in the element
250
+ #
251
+ # === Methods for Querying
252
+ #
253
+ # #document:: Returns the document, if any, that the element belongs to.
254
+ # #root:: Returns the most distant element (not document) ancestor of the element.
255
+ # #root_node:: Returns the most distant ancestor of the element.
256
+ # #xpath:: Returns the string xpath to the element
257
+ # relative to the most distant parent
258
+ # #has_attributes?:: Returns whether the element has attributes.
259
+ # #has_elements?:: Returns whether the element has elements.
260
+ # #has_text?:: Returns whether the element has text.
261
+ # #next_element:: Returns the next sibling that is an element.
262
+ # #previous_element:: Returns the previous sibling that is an element.
263
+ # #raw:: Returns whether raw mode is set for the element.
264
+ # #whitespace:: Returns whether whitespace is respected for the element.
265
+ # #ignore_whitespace_nodes:: Returns whether whitespace nodes
266
+ # are to be ignored for the element.
267
+ # #node_type:: Returns symbol <tt>:element</tt>.
268
+ #
269
+ # === One More Method
270
+ #
271
+ # #inspect:: Returns a string representation of the element.
272
+ #
273
+ # === Accessors
274
+ #
275
+ # #elements:: Returns the REXML::Elements object for the element.
276
+ # #attributes:: Returns the REXML::Attributes object for the element.
277
+ # #context:: Returns or sets the context hash for the element.
278
+ #
279
+ class Element < Parent
280
+ include Namespace
281
+
282
+ UNDEFINED = "UNDEFINED"; # The default name
283
+
284
+ # Mechanisms for accessing attributes and child elements of this
285
+ # element.
286
+ attr_reader :attributes, :elements
287
+ # The context holds information about the processing environment, such as
288
+ # whitespace handling.
289
+ attr_accessor :context
290
+
291
+ # :call-seq:
292
+ # Element.new(name = 'UNDEFINED', parent = nil, context = nil) -> new_element
293
+ # Element.new(element, parent = nil, context = nil) -> new_element
294
+ #
295
+ # Returns a new \REXML::Element object.
296
+ #
297
+ # When no arguments are given,
298
+ # returns an element with name <tt>'UNDEFINED'</tt>:
299
+ #
300
+ # e = REXML::Element.new # => <UNDEFINED/>
301
+ # e.class # => REXML::Element
302
+ # e.name # => "UNDEFINED"
303
+ #
304
+ # When only argument +name+ is given,
305
+ # returns an element of the given name:
306
+ #
307
+ # REXML::Element.new('foo') # => <foo/>
308
+ #
309
+ # When only argument +element+ is given, it must be an \REXML::Element object;
310
+ # returns a shallow copy of the given element:
311
+ #
312
+ # e0 = REXML::Element.new('foo')
313
+ # e1 = REXML::Element.new(e0) # => <foo/>
314
+ #
315
+ # When argument +parent+ is also given, it must be an REXML::Parent object:
316
+ #
317
+ # e = REXML::Element.new('foo', REXML::Parent.new)
318
+ # e.parent # => #<REXML::Parent @parent=nil, @children=[<foo/>]>
319
+ #
320
+ # When argument +context+ is also given, it must be a hash
321
+ # representing the context for the element;
322
+ # see {Element Context}[../doc/rexml/context_rdoc.html]:
323
+ #
324
+ # e = REXML::Element.new('foo', nil, {raw: :all})
325
+ # e.context # => {:raw=>:all}
326
+ #
327
+ def initialize( arg = UNDEFINED, parent=nil, context=nil )
328
+ super(parent)
329
+
330
+ @elements = Elements.new(self)
331
+ @attributes = Attributes.new(self)
332
+ @context = context
333
+
334
+ if arg.kind_of? String
335
+ self.name = arg
336
+ elsif arg.kind_of? Element
337
+ self.name = arg.expanded_name
338
+ arg.attributes.each_attribute{ |attribute|
339
+ @attributes << Attribute.new( attribute )
340
+ }
341
+ @context = arg.context
342
+ end
343
+ end
344
+
345
+ # :call-seq:
346
+ # inspect -> string
347
+ #
348
+ # Returns a string representation of the element.
349
+ #
350
+ # For an element with no attributes and no children, shows the element name:
351
+ #
352
+ # REXML::Element.new.inspect # => "<UNDEFINED/>"
353
+ #
354
+ # Shows attributes, if any:
355
+ #
356
+ # e = REXML::Element.new('foo')
357
+ # e.add_attributes({'bar' => 0, 'baz' => 1})
358
+ # e.inspect # => "<foo bar='0' baz='1'/>"
359
+ #
360
+ # Shows an ellipsis (<tt>...</tt>), if there are child elements:
361
+ #
362
+ # e.add_element(REXML::Element.new('bar'))
363
+ # e.add_element(REXML::Element.new('baz'))
364
+ # e.inspect # => "<foo bar='0' baz='1'> ... </>"
365
+ #
366
+ def inspect
367
+ rv = "<#@expanded_name"
368
+
369
+ @attributes.each_attribute do |attr|
370
+ rv << " "
371
+ attr.write( rv, 0 )
372
+ end
373
+
374
+ if children.size > 0
375
+ rv << "> ... </>"
376
+ else
377
+ rv << "/>"
378
+ end
379
+ end
380
+
381
+ # :call-seq:
382
+ # clone -> new_element
383
+ #
384
+ # Returns a shallow copy of the element, containing the name and attributes,
385
+ # but not the parent or children:
386
+ #
387
+ # e = REXML::Element.new('foo')
388
+ # e.add_attributes({'bar' => 0, 'baz' => 1})
389
+ # e.clone # => <foo bar='0' baz='1'/>
390
+ #
391
+ def clone
392
+ self.class.new self
393
+ end
394
+
395
+ # :call-seq:
396
+ # root_node -> document or element
397
+ #
398
+ # Returns the most distant ancestor of +self+.
399
+ #
400
+ # When the element is part of a document,
401
+ # returns the root node of the document.
402
+ # Note that the root node is different from the document element;
403
+ # in this example +a+ is document element and the root node is its parent:
404
+ #
405
+ # d = REXML::Document.new('<a><b><c/></b></a>')
406
+ # top_element = d.first # => <a> ... </>
407
+ # child = top_element.first # => <b> ... </>
408
+ # d.root_node == d # => true
409
+ # top_element.root_node == d # => true
410
+ # child.root_node == d # => true
411
+ #
412
+ # When the element is not part of a document, but does have ancestor elements,
413
+ # returns the most distant ancestor element:
414
+ #
415
+ # e0 = REXML::Element.new('foo')
416
+ # e1 = REXML::Element.new('bar')
417
+ # e1.parent = e0
418
+ # e2 = REXML::Element.new('baz')
419
+ # e2.parent = e1
420
+ # e2.root_node == e0 # => true
421
+ #
422
+ # When the element has no ancestor elements,
423
+ # returns +self+:
424
+ #
425
+ # e = REXML::Element.new('foo')
426
+ # e.root_node == e # => true
427
+ #
428
+ # Related: #root, #document.
429
+ #
430
+ def root_node
431
+ parent.nil? ? self : parent.root_node
432
+ end
433
+
434
+ # :call-seq:
435
+ # root -> element
436
+ #
437
+ # Returns the most distant _element_ (not document) ancestor of the element:
438
+ #
439
+ # d = REXML::Document.new('<a><b><c/></b></a>')
440
+ # top_element = d.first
441
+ # child = top_element.first
442
+ # top_element.root == top_element # => true
443
+ # child.root == top_element # => true
444
+ #
445
+ # For a document, returns the topmost element:
446
+ #
447
+ # d.root == top_element # => true
448
+ #
449
+ # Related: #root_node, #document.
450
+ #
451
+ def root
452
+ return elements[1] if self.kind_of? Document
453
+ return self if parent.kind_of? Document or parent.nil?
454
+ return parent.root
455
+ end
456
+
457
+ # :call-seq:
458
+ # document -> document or nil
459
+ #
460
+ # If the element is part of a document, returns that document:
461
+ #
462
+ # d = REXML::Document.new('<a><b><c/></b></a>')
463
+ # top_element = d.first
464
+ # child = top_element.first
465
+ # top_element.document == d # => true
466
+ # child.document == d # => true
467
+ #
468
+ # If the element is not part of a document, returns +nil+:
469
+ #
470
+ # REXML::Element.new.document # => nil
471
+ #
472
+ # For a document, returns +self+:
473
+ #
474
+ # d.document == d # => true
475
+ #
476
+ # Related: #root, #root_node.
477
+ #
478
+ def document
479
+ rt = root
480
+ rt.parent if rt
481
+ end
482
+
483
+ # :call-seq:
484
+ # whitespace
485
+ #
486
+ # Returns +true+ if whitespace is respected for this element,
487
+ # +false+ otherwise.
488
+ #
489
+ # See {Element Context}[../doc/rexml/context_rdoc.html].
490
+ #
491
+ # The evaluation is tested against the element's +expanded_name+,
492
+ # and so is namespace-sensitive.
493
+ def whitespace
494
+ @whitespace = nil
495
+ if @context
496
+ if @context[:respect_whitespace]
497
+ @whitespace = (@context[:respect_whitespace] == :all or
498
+ @context[:respect_whitespace].include? expanded_name)
499
+ end
500
+ @whitespace = false if (@context[:compress_whitespace] and
501
+ (@context[:compress_whitespace] == :all or
502
+ @context[:compress_whitespace].include? expanded_name)
503
+ )
504
+ end
505
+ @whitespace = true unless @whitespace == false
506
+ @whitespace
507
+ end
508
+
509
+ # :call-seq:
510
+ # ignore_whitespace_nodes
511
+ #
512
+ # Returns +true+ if whitespace nodes are ignored for the element.
513
+ #
514
+ # See {Element Context}[../doc/rexml/context_rdoc.html].
515
+ #
516
+ def ignore_whitespace_nodes
517
+ @ignore_whitespace_nodes = false
518
+ if @context
519
+ if @context[:ignore_whitespace_nodes]
520
+ @ignore_whitespace_nodes =
521
+ (@context[:ignore_whitespace_nodes] == :all or
522
+ @context[:ignore_whitespace_nodes].include? expanded_name)
523
+ end
524
+ end
525
+ end
526
+
527
+ # :call-seq:
528
+ # raw
529
+ #
530
+ # Returns +true+ if raw mode is set for the element.
531
+ #
532
+ # See {Element Context}[../doc/rexml/context_rdoc.html].
533
+ #
534
+ # The evaluation is tested against +expanded_name+, and so is namespace
535
+ # sensitive.
536
+ def raw
537
+ @raw = (@context and @context[:raw] and
538
+ (@context[:raw] == :all or
539
+ @context[:raw].include? expanded_name))
540
+ @raw
541
+ end
542
+
543
+ #once :whitespace, :raw, :ignore_whitespace_nodes
544
+
545
+ #################################################
546
+ # Namespaces #
547
+ #################################################
548
+
549
+ # :call-seq:
550
+ # prefixes -> array_of_namespace_prefixes
551
+ #
552
+ # Returns an array of the string prefixes (names) of all defined namespaces
553
+ # in the element and its ancestors:
554
+ #
555
+ # xml_string = <<-EOT
556
+ # <root>
557
+ # <a xmlns:x='1' xmlns:y='2'>
558
+ # <b/>
559
+ # <c xmlns:z='3'/>
560
+ # </a>
561
+ # </root>
562
+ # EOT
563
+ # d = REXML::Document.new(xml_string, {compress_whitespace: :all})
564
+ # d.elements['//a'].prefixes # => ["x", "y"]
565
+ # d.elements['//b'].prefixes # => ["x", "y"]
566
+ # d.elements['//c'].prefixes # => ["x", "y", "z"]
567
+ #
568
+ def prefixes
569
+ prefixes = []
570
+ prefixes = parent.prefixes if parent
571
+ prefixes |= attributes.prefixes
572
+ return prefixes
573
+ end
574
+
575
+ # :call-seq:
576
+ # namespaces -> array_of_namespace_names
577
+ #
578
+ # Returns a hash of all defined namespaces
579
+ # in the element and its ancestors:
580
+ #
581
+ # xml_string = <<-EOT
582
+ # <root>
583
+ # <a xmlns:x='1' xmlns:y='2'>
584
+ # <b/>
585
+ # <c xmlns:z='3'/>
586
+ # </a>
587
+ # </root>
588
+ # EOT
589
+ # d = REXML::Document.new(xml_string)
590
+ # d.elements['//a'].namespaces # => {"x"=>"1", "y"=>"2"}
591
+ # d.elements['//b'].namespaces # => {"x"=>"1", "y"=>"2"}
592
+ # d.elements['//c'].namespaces # => {"x"=>"1", "y"=>"2", "z"=>"3"}
593
+ #
594
+ def namespaces
595
+ namespaces = {}
596
+ namespaces = parent.namespaces if parent
597
+ namespaces = namespaces.merge( attributes.namespaces )
598
+ return namespaces
599
+ end
600
+
601
+ # :call-seq:
602
+ # namespace(prefix = nil) -> string_uri or nil
603
+ #
604
+ # Returns the string namespace URI for the element,
605
+ # possibly deriving from one of its ancestors.
606
+ #
607
+ # xml_string = <<-EOT
608
+ # <root>
609
+ # <a xmlns='1' xmlns:y='2'>
610
+ # <b/>
611
+ # <c xmlns:z='3'/>
612
+ # </a>
613
+ # </root>
614
+ # EOT
615
+ # d = REXML::Document.new(xml_string)
616
+ # b = d.elements['//b']
617
+ # b.namespace # => "1"
618
+ # b.namespace('y') # => "2"
619
+ # b.namespace('nosuch') # => nil
620
+ #
621
+ def namespace(prefix=nil)
622
+ if prefix.nil?
623
+ prefix = prefix()
624
+ end
625
+ if prefix == ''
626
+ prefix = "xmlns"
627
+ else
628
+ prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
629
+ end
630
+ ns = attributes[ prefix ]
631
+ ns = parent.namespace(prefix) if ns.nil? and parent
632
+ ns = '' if ns.nil? and prefix == 'xmlns'
633
+ return ns
634
+ end
635
+
636
+ # :call-seq:
637
+ # add_namespace(prefix, uri = nil) -> self
638
+ #
639
+ # Adds a namespace to the element; returns +self+.
640
+ #
641
+ # With the single argument +prefix+,
642
+ # adds a namespace using the given +prefix+ and the namespace URI:
643
+ #
644
+ # e = REXML::Element.new('foo')
645
+ # e.add_namespace('bar')
646
+ # e.namespaces # => {"xmlns"=>"bar"}
647
+ #
648
+ # With both arguments +prefix+ and +uri+ given,
649
+ # adds a namespace using both arguments:
650
+ #
651
+ # e.add_namespace('baz', 'bat')
652
+ # e.namespaces # => {"xmlns"=>"bar", "baz"=>"bat"}
653
+ #
654
+ def add_namespace( prefix, uri=nil )
655
+ unless uri
656
+ @attributes["xmlns"] = prefix
657
+ else
658
+ prefix = "xmlns:#{prefix}" unless prefix =~ /^xmlns:/
659
+ @attributes[ prefix ] = uri
660
+ end
661
+ self
662
+ end
663
+
664
+ # :call-seq:
665
+ # delete_namespace(namespace = 'xmlns') -> self
666
+ #
667
+ # Removes a namespace from the element.
668
+ #
669
+ # With no argument, removes the default namespace:
670
+ #
671
+ # d = REXML::Document.new "<a xmlns:foo='bar' xmlns='twiddle'/>"
672
+ # d.to_s # => "<a xmlns:foo='bar' xmlns='twiddle'/>"
673
+ # d.root.delete_namespace # => <a xmlns:foo='bar'/>
674
+ # d.to_s # => "<a xmlns:foo='bar'/>"
675
+ #
676
+ # With argument +namespace+, removes the specified namespace:
677
+ #
678
+ # d.root.delete_namespace('foo')
679
+ # d.to_s # => "<a/>"
680
+ #
681
+ # Does nothing if no such namespace is found:
682
+ #
683
+ # d.root.delete_namespace('nosuch')
684
+ # d.to_s # => "<a/>"
685
+ #
686
+ def delete_namespace namespace="xmlns"
687
+ namespace = "xmlns:#{namespace}" unless namespace == 'xmlns'
688
+ attribute = attributes.get_attribute(namespace)
689
+ attribute.remove unless attribute.nil?
690
+ self
691
+ end
692
+
693
+ #################################################
694
+ # Elements #
695
+ #################################################
696
+
697
+ # :call-seq:
698
+ # add_element(name, attributes = nil) -> new_element
699
+ # add_element(element, attributes = nil) -> element
700
+ #
701
+ # Adds a child element, optionally setting attributes
702
+ # on the added element; returns the added element.
703
+ #
704
+ # With string argument +name+, creates a new element with that name
705
+ # and adds the new element as a child:
706
+ #
707
+ # e0 = REXML::Element.new('foo')
708
+ # e0.add_element('bar')
709
+ # e0[0] # => <bar/>
710
+ #
711
+ #
712
+ # With argument +name+ and hash argument +attributes+,
713
+ # sets attributes on the new element:
714
+ #
715
+ # e0.add_element('baz', {'bat' => '0', 'bam' => '1'})
716
+ # e0[1] # => <baz bat='0' bam='1'/>
717
+ #
718
+ # With element argument +element+, adds that element as a child:
719
+ #
720
+ # e0 = REXML::Element.new('foo')
721
+ # e1 = REXML::Element.new('bar')
722
+ # e0.add_element(e1)
723
+ # e0[0] # => <bar/>
724
+ #
725
+ # With argument +element+ and hash argument +attributes+,
726
+ # sets attributes on the added element:
727
+ #
728
+ # e0.add_element(e1, {'bat' => '0', 'bam' => '1'})
729
+ # e0[1] # => <bar bat='0' bam='1'/>
730
+ #
731
+ def add_element element, attrs=nil
732
+ raise "First argument must be either an element name, or an Element object" if element.nil?
733
+ el = @elements.add(element)
734
+ attrs.each do |key, value|
735
+ el.attributes[key]=value
736
+ end if attrs.kind_of? Hash
737
+ el
738
+ end
739
+
740
+ # :call-seq:
741
+ # delete_element(index) -> removed_element or nil
742
+ # delete_element(element) -> removed_element or nil
743
+ # delete_element(xpath) -> removed_element or nil
744
+ #
745
+ # Deletes a child element.
746
+ #
747
+ # When 1-based integer argument +index+ is given,
748
+ # removes and returns the child element at that offset if it exists;
749
+ # indexing does not include text nodes;
750
+ # returns +nil+ if the element does not exist:
751
+ #
752
+ # d = REXML::Document.new '<a><b/>text<c/></a>'
753
+ # a = d.root # => <a> ... </>
754
+ # a.delete_element(1) # => <b/>
755
+ # a.delete_element(1) # => <c/>
756
+ # a.delete_element(1) # => nil
757
+ #
758
+ # When element argument +element+ is given,
759
+ # removes and returns that child element if it exists,
760
+ # otherwise returns +nil+:
761
+ #
762
+ # d = REXML::Document.new '<a><b/>text<c/></a>'
763
+ # a = d.root # => <a> ... </>
764
+ # c = a[2] # => <c/>
765
+ # a.delete_element(c) # => <c/>
766
+ # a.delete_element(c) # => nil
767
+ #
768
+ # When xpath argument +xpath+ is given,
769
+ # removes and returns the element at xpath if it exists,
770
+ # otherwise returns +nil+:
771
+ #
772
+ # d = REXML::Document.new '<a><b/>text<c/></a>'
773
+ # a = d.root # => <a> ... </>
774
+ # a.delete_element('//c') # => <c/>
775
+ # a.delete_element('//c') # => nil
776
+ #
777
+ def delete_element element
778
+ @elements.delete element
779
+ end
780
+
781
+ # :call-seq:
782
+ # has_elements?
783
+ #
784
+ # Returns +true+ if the element has one or more element children,
785
+ # +false+ otherwise:
786
+ #
787
+ # d = REXML::Document.new '<a><b/>text<c/></a>'
788
+ # a = d.root # => <a> ... </>
789
+ # a.has_elements? # => true
790
+ # b = a[0] # => <b/>
791
+ # b.has_elements? # => false
792
+ #
793
+ def has_elements?
794
+ !@elements.empty?
795
+ end
796
+
797
+ # :call-seq:
798
+ # each_element_with_attribute(attr_name, value = nil, max = 0, xpath = nil) {|e| ... }
799
+ #
800
+ # Calls the given block with each child element that meets given criteria.
801
+ #
802
+ # When only string argument +attr_name+ is given,
803
+ # calls the block with each child element that has that attribute:
804
+ #
805
+ # d = REXML::Document.new '<a><b id="1"/><c id="2"/><d id="1"/><e/></a>'
806
+ # a = d.root
807
+ # a.each_element_with_attribute('id') {|e| p e }
808
+ #
809
+ # Output:
810
+ #
811
+ # <b id='1'/>
812
+ # <c id='2'/>
813
+ # <d id='1'/>
814
+ #
815
+ # With argument +attr_name+ and string argument +value+ given,
816
+ # calls the block with each child element that has that attribute
817
+ # with that value:
818
+ #
819
+ # a.each_element_with_attribute('id', '1') {|e| p e }
820
+ #
821
+ # Output:
822
+ #
823
+ # <b id='1'/>
824
+ # <d id='1'/>
825
+ #
826
+ # With arguments +attr_name+, +value+, and integer argument +max+ given,
827
+ # calls the block with at most +max+ child elements:
828
+ #
829
+ # a.each_element_with_attribute('id', '1', 1) {|e| p e }
830
+ #
831
+ # Output:
832
+ #
833
+ # <b id='1'/>
834
+ #
835
+ # With all arguments given, including +xpath+,
836
+ # calls the block with only those child elements
837
+ # that meet the first three criteria,
838
+ # and also match the given +xpath+:
839
+ #
840
+ # a.each_element_with_attribute('id', '1', 2, '//d') {|e| p e }
841
+ #
842
+ # Output:
843
+ #
844
+ # <d id='1'/>
845
+ #
846
+ def each_element_with_attribute( key, value=nil, max=0, name=nil, &block ) # :yields: Element
847
+ each_with_something( proc {|child|
848
+ if value.nil?
849
+ child.attributes[key] != nil
850
+ else
851
+ child.attributes[key]==value
852
+ end
853
+ }, max, name, &block )
854
+ end
855
+
856
+ # :call-seq:
857
+ # each_element_with_text(text = nil, max = 0, xpath = nil) {|e| ... }
858
+ #
859
+ # Calls the given block with each child element that meets given criteria.
860
+ #
861
+ # With no arguments, calls the block with each child element that has text:
862
+ #
863
+ # d = REXML::Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
864
+ # a = d.root
865
+ # a.each_element_with_text {|e| p e }
866
+ #
867
+ # Output:
868
+ #
869
+ # <b> ... </>
870
+ # <c> ... </>
871
+ # <d> ... </>
872
+ #
873
+ # With the single string argument +text+,
874
+ # calls the block with each element that has exactly that text:
875
+ #
876
+ # a.each_element_with_text('b') {|e| p e }
877
+ #
878
+ # Output:
879
+ #
880
+ # <b> ... </>
881
+ # <c> ... </>
882
+ #
883
+ # With argument +text+ and integer argument +max+,
884
+ # calls the block with at most +max+ elements:
885
+ #
886
+ # a.each_element_with_text('b', 1) {|e| p e }
887
+ #
888
+ # Output:
889
+ #
890
+ # <b> ... </>
891
+ #
892
+ # With all arguments given, including +xpath+,
893
+ # calls the block with only those child elements
894
+ # that meet the first two criteria,
895
+ # and also match the given +xpath+:
896
+ #
897
+ # a.each_element_with_text('b', 2, '//c') {|e| p e }
898
+ #
899
+ # Output:
900
+ #
901
+ # <c> ... </>
902
+ #
903
+ def each_element_with_text( text=nil, max=0, name=nil, &block ) # :yields: Element
904
+ each_with_something( proc {|child|
905
+ if text.nil?
906
+ child.has_text?
907
+ else
908
+ child.text == text
909
+ end
910
+ }, max, name, &block )
911
+ end
912
+
913
+ # :call-seq:
914
+ # each_element {|e| ... }
915
+ #
916
+ # Calls the given block with each child element:
917
+ #
918
+ # d = REXML::Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
919
+ # a = d.root
920
+ # a.each_element {|e| p e }
921
+ #
922
+ # Output:
923
+ #
924
+ # <b> ... </>
925
+ # <c> ... </>
926
+ # <d> ... </>
927
+ # <e/>
928
+ #
929
+ def each_element( xpath=nil, &block ) # :yields: Element
930
+ @elements.each( xpath, &block )
931
+ end
932
+
933
+ # :call-seq:
934
+ # get_elements(xpath)
935
+ #
936
+ # Returns an array of the elements that match the given +xpath+:
937
+ #
938
+ # xml_string = <<-EOT
939
+ # <root>
940
+ # <a level='1'>
941
+ # <a level='2'/>
942
+ # </a>
943
+ # </root>
944
+ # EOT
945
+ # d = REXML::Document.new(xml_string)
946
+ # d.root.get_elements('//a') # => [<a level='1'> ... </>, <a level='2'/>]
947
+ #
948
+ def get_elements( xpath )
949
+ @elements.to_a( xpath )
950
+ end
951
+
952
+ # :call-seq:
953
+ # next_element
954
+ #
955
+ # Returns the next sibling that is an element if it exists,
956
+ # +niL+ otherwise:
957
+ #
958
+ # d = REXML::Document.new '<a><b/>text<c/></a>'
959
+ # d.root.elements['b'].next_element #-> <c/>
960
+ # d.root.elements['c'].next_element #-> nil
961
+ #
962
+ def next_element
963
+ element = next_sibling
964
+ element = element.next_sibling until element.nil? or element.kind_of? Element
965
+ return element
966
+ end
967
+
968
+ # :call-seq:
969
+ # previous_element
970
+ #
971
+ # Returns the previous sibling that is an element if it exists,
972
+ # +niL+ otherwise:
973
+ #
974
+ # d = REXML::Document.new '<a><b/>text<c/></a>'
975
+ # d.root.elements['c'].previous_element #-> <b/>
976
+ # d.root.elements['b'].previous_element #-> nil
977
+ #
978
+ def previous_element
979
+ element = previous_sibling
980
+ element = element.previous_sibling until element.nil? or element.kind_of? Element
981
+ return element
982
+ end
983
+
984
+
985
+ #################################################
986
+ # Text #
987
+ #################################################
988
+
989
+ # :call-seq:
990
+ # has_text? -> true or false
991
+ #
992
+ # Returns +true if the element has one or more text noded,
993
+ # +false+ otherwise:
994
+ #
995
+ # d = REXML::Document.new '<a><b/>text<c/></a>'
996
+ # a = d.root
997
+ # a.has_text? # => true
998
+ # b = a[0]
999
+ # b.has_text? # => false
1000
+ #
1001
+ def has_text?
1002
+ not text().nil?
1003
+ end
1004
+
1005
+ # :call-seq:
1006
+ # text(xpath = nil) -> text_string or nil
1007
+ #
1008
+ # Returns the text string from the first text node child
1009
+ # in a specified element, if it exists, # +nil+ otherwise.
1010
+ #
1011
+ # With no argument, returns the text from the first text node in +self+:
1012
+ #
1013
+ # d = REXML::Document.new "<p>some text <b>this is bold!</b> more text</p>"
1014
+ # d.root.text.class # => String
1015
+ # d.root.text # => "some text "
1016
+ #
1017
+ # With argument +xpath+, returns text from the the first text node
1018
+ # in the element that matches +xpath+:
1019
+ #
1020
+ # d.root.text(1) # => "this is bold!"
1021
+ #
1022
+ # Note that an element may have multiple text nodes,
1023
+ # possibly separated by other non-text children, as above.
1024
+ # Even so, the returned value is the string text from the first such node.
1025
+ #
1026
+ # Note also that the text note is retrieved by method get_text,
1027
+ # and so is always normalized text.
1028
+ #
1029
+ def text( path = nil )
1030
+ rv = get_text(path)
1031
+ return rv.value unless rv.nil?
1032
+ nil
1033
+ end
1034
+
1035
+ # :call-seq:
1036
+ # get_text(xpath = nil) -> text_node or nil
1037
+ #
1038
+ # Returns the first text node child in a specified element, if it exists,
1039
+ # +nil+ otherwise.
1040
+ #
1041
+ # With no argument, returns the first text node from +self+:
1042
+ #
1043
+ # d = REXML::Document.new "<p>some text <b>this is bold!</b> more text</p>"
1044
+ # d.root.get_text.class # => REXML::Text
1045
+ # d.root.get_text # => "some text "
1046
+ #
1047
+ # With argument +xpath+, returns the first text node from the element
1048
+ # that matches +xpath+:
1049
+ #
1050
+ # d.root.get_text(1) # => "this is bold!"
1051
+ #
1052
+ def get_text path = nil
1053
+ rv = nil
1054
+ if path
1055
+ element = @elements[ path ]
1056
+ rv = element.get_text unless element.nil?
1057
+ else
1058
+ rv = @children.find { |node| node.kind_of? Text }
1059
+ end
1060
+ return rv
1061
+ end
1062
+
1063
+ # :call-seq:
1064
+ # text = string -> string
1065
+ # text = nil -> nil
1066
+ #
1067
+ # Adds, replaces, or removes the first text node child in the element.
1068
+ #
1069
+ # With string argument +string+,
1070
+ # creates a new \REXML::Text node containing that string,
1071
+ # honoring the current settings for whitespace and row,
1072
+ # then places the node as the first text child in the element;
1073
+ # returns +string+.
1074
+ #
1075
+ # If the element has no text child, the text node is added:
1076
+ #
1077
+ # d = REXML::Document.new '<a><b/></a>'
1078
+ # d.root.text = 'foo' #-> '<a><b/>foo</a>'
1079
+ #
1080
+ # If the element has a text child, it is replaced:
1081
+ #
1082
+ # d.root.text = 'bar' #-> '<a><b/>bar</a>'
1083
+ #
1084
+ # With argument +nil+, removes the first text child:
1085
+ #
1086
+ # d.root.text = nil #-> '<a><b/><c/></a>'
1087
+ #
1088
+ def text=( text )
1089
+ if text.kind_of? String
1090
+ text = Text.new( text, whitespace(), nil, raw() )
1091
+ elsif !text.nil? and !text.kind_of? Text
1092
+ text = Text.new( text.to_s, whitespace(), nil, raw() )
1093
+ end
1094
+ old_text = get_text
1095
+ if text.nil?
1096
+ old_text.remove unless old_text.nil?
1097
+ else
1098
+ if old_text.nil?
1099
+ self << text
1100
+ else
1101
+ old_text.replace_with( text )
1102
+ end
1103
+ end
1104
+ return self
1105
+ end
1106
+
1107
+ # :call-seq:
1108
+ # add_text(string) -> nil
1109
+ # add_text(text_node) -> self
1110
+ #
1111
+ # Adds text to the element.
1112
+ #
1113
+ # When string argument +string+ is given, returns +nil+.
1114
+ #
1115
+ # If the element has no child text node,
1116
+ # creates a \REXML::Text object using the string,
1117
+ # honoring the current settings for whitespace and raw,
1118
+ # then adds that node to the element:
1119
+ #
1120
+ # d = REXML::Document.new('<a><b/></a>')
1121
+ # a = d.root
1122
+ # a.add_text('foo')
1123
+ # a.to_a # => [<b/>, "foo"]
1124
+ #
1125
+ # If the element has child text nodes,
1126
+ # appends the string to the _last_ text node:
1127
+ #
1128
+ # d = REXML::Document.new('<a>foo<b/>bar</a>')
1129
+ # a = d.root
1130
+ # a.add_text('baz')
1131
+ # a.to_a # => ["foo", <b/>, "barbaz"]
1132
+ # a.add_text('baz')
1133
+ # a.to_a # => ["foo", <b/>, "barbazbaz"]
1134
+ #
1135
+ # When text node argument +text_node+ is given,
1136
+ # appends the node as the last text node in the element;
1137
+ # returns +self+:
1138
+ #
1139
+ # d = REXML::Document.new('<a>foo<b/>bar</a>')
1140
+ # a = d.root
1141
+ # a.add_text(REXML::Text.new('baz'))
1142
+ # a.to_a # => ["foo", <b/>, "bar", "baz"]
1143
+ # a.add_text(REXML::Text.new('baz'))
1144
+ # a.to_a # => ["foo", <b/>, "bar", "baz", "baz"]
1145
+ #
1146
+ def add_text( text )
1147
+ if text.kind_of? String
1148
+ if @children[-1].kind_of? Text
1149
+ @children[-1] << text
1150
+ return
1151
+ end
1152
+ text = Text.new( text, whitespace(), nil, raw() )
1153
+ end
1154
+ self << text unless text.nil?
1155
+ return self
1156
+ end
1157
+
1158
+ # :call-seq:
1159
+ # node_type -> :element
1160
+ #
1161
+ # Returns symbol <tt>:element</tt>:
1162
+ #
1163
+ # d = REXML::Document.new('<a/>')
1164
+ # a = d.root # => <a/>
1165
+ # a.node_type # => :element
1166
+ #
1167
+ def node_type
1168
+ :element
1169
+ end
1170
+
1171
+ # :call-seq:
1172
+ # xpath -> string_xpath
1173
+ #
1174
+ # Returns the string xpath to the element
1175
+ # relative to the most distant parent:
1176
+ #
1177
+ # d = REXML::Document.new('<a><b><c/></b></a>')
1178
+ # a = d.root # => <a> ... </>
1179
+ # b = a[0] # => <b> ... </>
1180
+ # c = b[0] # => <c/>
1181
+ # d.xpath # => ""
1182
+ # a.xpath # => "/a"
1183
+ # b.xpath # => "/a/b"
1184
+ # c.xpath # => "/a/b/c"
1185
+ #
1186
+ # If there is no parent, returns the expanded name of the element:
1187
+ #
1188
+ # e = REXML::Element.new('foo')
1189
+ # e.xpath # => "foo"
1190
+ #
1191
+ def xpath
1192
+ path_elements = []
1193
+ cur = self
1194
+ path_elements << __to_xpath_helper( self )
1195
+ while cur.parent
1196
+ cur = cur.parent
1197
+ path_elements << __to_xpath_helper( cur )
1198
+ end
1199
+ return path_elements.reverse.join( "/" )
1200
+ end
1201
+
1202
+ #################################################
1203
+ # Attributes #
1204
+ #################################################
1205
+
1206
+ # :call-seq:
1207
+ # [index] -> object
1208
+ # [attr_name] -> attr_value
1209
+ # [attr_sym] -> attr_value
1210
+ #
1211
+ # With integer argument +index+ given,
1212
+ # returns the child at offset +index+, or +nil+ if none:
1213
+ #
1214
+ # d = REXML::Document.new '><root><a/>text<b/>more<c/></root>'
1215
+ # root = d.root
1216
+ # (0..root.size).each do |index|
1217
+ # node = root[index]
1218
+ # p "#{index}: #{node} (#{node.class})"
1219
+ # end
1220
+ #
1221
+ # Output:
1222
+ #
1223
+ # "0: <a/> (REXML::Element)"
1224
+ # "1: text (REXML::Text)"
1225
+ # "2: <b/> (REXML::Element)"
1226
+ # "3: more (REXML::Text)"
1227
+ # "4: <c/> (REXML::Element)"
1228
+ # "5: (NilClass)"
1229
+ #
1230
+ # With string argument +attr_name+ given,
1231
+ # returns the string value for the given attribute name if it exists,
1232
+ # otherwise +nil+:
1233
+ #
1234
+ # d = REXML::Document.new('<root attr="value"></root>')
1235
+ # root = d.root
1236
+ # root['attr'] # => "value"
1237
+ # root['nosuch'] # => nil
1238
+ #
1239
+ # With symbol argument +attr_sym+ given,
1240
+ # returns <tt>[attr_sym.to_s]</tt>:
1241
+ #
1242
+ # root[:attr] # => "value"
1243
+ # root[:nosuch] # => nil
1244
+ #
1245
+ def [](name_or_index)
1246
+ case name_or_index
1247
+ when String
1248
+ attributes[name_or_index]
1249
+ when Symbol
1250
+ attributes[name_or_index.to_s]
1251
+ else
1252
+ super
1253
+ end
1254
+ end
1255
+
1256
+
1257
+ # :call-seq:
1258
+ # attribute(name, namespace = nil)
1259
+ #
1260
+ # Returns the string value for the given attribute name.
1261
+ #
1262
+ # With only argument +name+ given,
1263
+ # returns the value of the named attribute if it exists, otherwise +nil+:
1264
+ #
1265
+ # xml_string = <<-EOT
1266
+ # <root xmlns="ns0">
1267
+ # <a xmlns="ns1" attr="value"></a>
1268
+ # <b xmlns="ns2" attr="value"></b>
1269
+ # <c attr="value"/>
1270
+ # </root>
1271
+ # EOT
1272
+ # d = REXML::Document.new(xml_string)
1273
+ # root = d.root
1274
+ # a = root[1] # => <a xmlns='ns1' attr='value'/>
1275
+ # a.attribute('attr') # => attr='value'
1276
+ # a.attribute('nope') # => nil
1277
+ #
1278
+ # With arguments +name+ and +namespace+ given,
1279
+ # returns the value of the named attribute if it exists, otherwise +nil+:
1280
+ #
1281
+ # xml_string = "<root xmlns:a='a' a:x='a:x' x='x'/>"
1282
+ # document = REXML::Document.new(xml_string)
1283
+ # document.root.attribute("x") # => x='x'
1284
+ # document.root.attribute("x", "a") # => a:x='a:x'
1285
+ #
1286
+ def attribute( name, namespace=nil )
1287
+ prefix = nil
1288
+ if namespaces.respond_to? :key
1289
+ prefix = namespaces.key(namespace) if namespace
1290
+ else
1291
+ prefix = namespaces.index(namespace) if namespace
1292
+ end
1293
+ prefix = nil if prefix == 'xmlns'
1294
+
1295
+ ret_val =
1296
+ attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
1297
+
1298
+ return ret_val unless ret_val.nil?
1299
+ return nil if prefix.nil?
1300
+
1301
+ # now check that prefix'es namespace is not the same as the
1302
+ # default namespace
1303
+ return nil unless ( namespaces[ prefix ] == namespaces[ 'xmlns' ] )
1304
+
1305
+ attributes.get_attribute( name )
1306
+
1307
+ end
1308
+
1309
+ # :call-seq:
1310
+ # has_attributes? -> true or false
1311
+ #
1312
+ # Returns +true+ if the element has attributes, +false+ otherwise:
1313
+ #
1314
+ # d = REXML::Document.new('<root><a attr="val"/><b/></root>')
1315
+ # a, b = *d.root
1316
+ # a.has_attributes? # => true
1317
+ # b.has_attributes? # => false
1318
+ #
1319
+ def has_attributes?
1320
+ return !@attributes.empty?
1321
+ end
1322
+
1323
+ # :call-seq:
1324
+ # add_attribute(name, value) -> value
1325
+ # add_attribute(attribute) -> attribute
1326
+ #
1327
+ # Adds an attribute to this element, overwriting any existing attribute
1328
+ # by the same name.
1329
+ #
1330
+ # With string argument +name+ and object +value+ are given,
1331
+ # adds the attribute created with that name and value:
1332
+ #
1333
+ # e = REXML::Element.new
1334
+ # e.add_attribute('attr', 'value') # => "value"
1335
+ # e['attr'] # => "value"
1336
+ # e.add_attribute('attr', 'VALUE') # => "VALUE"
1337
+ # e['attr'] # => "VALUE"
1338
+ #
1339
+ # With only attribute object +attribute+ given,
1340
+ # adds the given attribute:
1341
+ #
1342
+ # a = REXML::Attribute.new('attr', 'value')
1343
+ # e.add_attribute(a) # => attr='value'
1344
+ # e['attr'] # => "value"
1345
+ # a = REXML::Attribute.new('attr', 'VALUE')
1346
+ # e.add_attribute(a) # => attr='VALUE'
1347
+ # e['attr'] # => "VALUE"
1348
+ #
1349
+ def add_attribute( key, value=nil )
1350
+ if key.kind_of? Attribute
1351
+ @attributes << key
1352
+ else
1353
+ @attributes[key] = value
1354
+ end
1355
+ end
1356
+
1357
+ # :call-seq:
1358
+ # add_attributes(hash) -> hash
1359
+ # add_attributes(array)
1360
+ #
1361
+ # Adds zero or more attributes to the element;
1362
+ # returns the argument.
1363
+ #
1364
+ # If hash argument +hash+ is given,
1365
+ # each key must be a string;
1366
+ # adds each attribute created with the key/value pair:
1367
+ #
1368
+ # e = REXML::Element.new
1369
+ # h = {'foo' => 'bar', 'baz' => 'bat'}
1370
+ # e.add_attributes(h)
1371
+ #
1372
+ # If argument +array+ is given,
1373
+ # each array member must be a 2-element array <tt>[name, value];
1374
+ # each name must be a string:
1375
+ #
1376
+ # e = REXML::Element.new
1377
+ # a = [['foo' => 'bar'], ['baz' => 'bat']]
1378
+ # e.add_attributes(a)
1379
+ #
1380
+ def add_attributes hash
1381
+ if hash.kind_of? Hash
1382
+ hash.each_pair {|key, value| @attributes[key] = value }
1383
+ elsif hash.kind_of? Array
1384
+ hash.each { |value| @attributes[ value[0] ] = value[1] }
1385
+ end
1386
+ end
1387
+
1388
+ # :call-seq:
1389
+ # delete_attribute(name) -> removed_attribute or nil
1390
+ #
1391
+ # Removes a named attribute if it exists;
1392
+ # returns the removed attribute if found, otherwise +nil+:
1393
+ #
1394
+ # e = REXML::Element.new('foo')
1395
+ # e.add_attribute('bar', 'baz')
1396
+ # e.delete_attribute('bar') # => <bar/>
1397
+ # e.delete_attribute('bar') # => nil
1398
+ #
1399
+ def delete_attribute(key)
1400
+ attr = @attributes.get_attribute(key)
1401
+ attr.remove unless attr.nil?
1402
+ end
1403
+
1404
+ #################################################
1405
+ # Other Utilities #
1406
+ #################################################
1407
+
1408
+ # :call-seq:
1409
+ # cdatas -> array_of_cdata_children
1410
+ #
1411
+ # Returns a frozen array of the REXML::CData children of the element:
1412
+ #
1413
+ # xml_string = <<-EOT
1414
+ # <root>
1415
+ # <![CDATA[foo]]>
1416
+ # <![CDATA[bar]]>
1417
+ # </root>
1418
+ # EOT
1419
+ # d = REXML::Document.new(xml_string)
1420
+ # cds = d.root.cdatas # => ["foo", "bar"]
1421
+ # cds.frozen? # => true
1422
+ # cds.map {|cd| cd.class } # => [REXML::CData, REXML::CData]
1423
+ #
1424
+ def cdatas
1425
+ find_all { |child| child.kind_of? CData }.freeze
1426
+ end
1427
+
1428
+ # :call-seq:
1429
+ # comments -> array_of_comment_children
1430
+ #
1431
+ # Returns a frozen array of the REXML::Comment children of the element:
1432
+ #
1433
+ # xml_string = <<-EOT
1434
+ # <root>
1435
+ # <!--foo-->
1436
+ # <!--bar-->
1437
+ # </root>
1438
+ # EOT
1439
+ # d = REXML::Document.new(xml_string)
1440
+ # cs = d.root.comments
1441
+ # cs.frozen? # => true
1442
+ # cs.map {|c| c.class } # => [REXML::Comment, REXML::Comment]
1443
+ # cs.map {|c| c.to_s } # => ["foo", "bar"]
1444
+ #
1445
+ def comments
1446
+ find_all { |child| child.kind_of? Comment }.freeze
1447
+ end
1448
+
1449
+ # :call-seq:
1450
+ # instructions -> array_of_instruction_children
1451
+ #
1452
+ # Returns a frozen array of the REXML::Instruction children of the element:
1453
+ #
1454
+ # xml_string = <<-EOT
1455
+ # <root>
1456
+ # <?target0 foo?>
1457
+ # <?target1 bar?>
1458
+ # </root>
1459
+ # EOT
1460
+ # d = REXML::Document.new(xml_string)
1461
+ # is = d.root.instructions
1462
+ # is.frozen? # => true
1463
+ # is.map {|i| i.class } # => [REXML::Instruction, REXML::Instruction]
1464
+ # is.map {|i| i.to_s } # => ["<?target0 foo?>", "<?target1 bar?>"]
1465
+ #
1466
+ def instructions
1467
+ find_all { |child| child.kind_of? Instruction }.freeze
1468
+ end
1469
+
1470
+ # :call-seq:
1471
+ # texts -> array_of_text_children
1472
+ #
1473
+ # Returns a frozen array of the REXML::Text children of the element:
1474
+ #
1475
+ # xml_string = '<root><a/>text<b/>more<c/></root>'
1476
+ # d = REXML::Document.new(xml_string)
1477
+ # ts = d.root.texts
1478
+ # ts.frozen? # => true
1479
+ # ts.map {|t| t.class } # => [REXML::Text, REXML::Text]
1480
+ # ts.map {|t| t.to_s } # => ["text", "more"]
1481
+ #
1482
+ def texts
1483
+ find_all { |child| child.kind_of? Text }.freeze
1484
+ end
1485
+
1486
+ # == DEPRECATED
1487
+ # See REXML::Formatters
1488
+ #
1489
+ # Writes out this element, and recursively, all children.
1490
+ # output::
1491
+ # output an object which supports '<< string'; this is where the
1492
+ # document will be written.
1493
+ # indent::
1494
+ # An integer. If -1, no indenting will be used; otherwise, the
1495
+ # indentation will be this number of spaces, and children will be
1496
+ # indented an additional amount. Defaults to -1
1497
+ # transitive::
1498
+ # If transitive is true and indent is >= 0, then the output will be
1499
+ # pretty-printed in such a way that the added whitespace does not affect
1500
+ # the parse tree of the document
1501
+ # ie_hack::
1502
+ # This hack inserts a space before the /> on empty tags to address
1503
+ # a limitation of Internet Explorer. Defaults to false
1504
+ #
1505
+ # out = ''
1506
+ # doc.write( out ) #-> doc is written to the string 'out'
1507
+ # doc.write( $stdout ) #-> doc written to the console
1508
+ def write(output=$stdout, indent=-1, transitive=false, ie_hack=false)
1509
+ Kernel.warn("#{self.class.name}.write is deprecated. See REXML::Formatters", uplevel: 1)
1510
+ formatter = if indent > -1
1511
+ if transitive
1512
+ require_relative "formatters/transitive"
1513
+ REXML::Formatters::Transitive.new( indent, ie_hack )
1514
+ else
1515
+ REXML::Formatters::Pretty.new( indent, ie_hack )
1516
+ end
1517
+ else
1518
+ REXML::Formatters::Default.new( ie_hack )
1519
+ end
1520
+ formatter.write( self, output )
1521
+ end
1522
+
1523
+
1524
+ private
1525
+ def __to_xpath_helper node
1526
+ rv = node.expanded_name.clone
1527
+ if node.parent
1528
+ results = node.parent.find_all {|n|
1529
+ n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name
1530
+ }
1531
+ if results.length > 1
1532
+ idx = results.index( node )
1533
+ rv << "[#{idx+1}]"
1534
+ end
1535
+ end
1536
+ rv
1537
+ end
1538
+
1539
+ # A private helper method
1540
+ def each_with_something( test, max=0, name=nil )
1541
+ num = 0
1542
+ @elements.each( name ){ |child|
1543
+ yield child if test.call(child) and num += 1
1544
+ return if max>0 and num == max
1545
+ }
1546
+ end
1547
+ end
1548
+
1549
+ ########################################################################
1550
+ # ELEMENTS #
1551
+ ########################################################################
1552
+
1553
+ # A class which provides filtering of children for Elements, and
1554
+ # XPath search support. You are expected to only encounter this class as
1555
+ # the <tt>element.elements</tt> object. Therefore, you are
1556
+ # _not_ expected to instantiate this yourself.
1557
+ #
1558
+ # xml_string = <<-EOT
1559
+ # <?xml version="1.0" encoding="UTF-8"?>
1560
+ # <bookstore>
1561
+ # <book category="cooking">
1562
+ # <title lang="en">Everyday Italian</title>
1563
+ # <author>Giada De Laurentiis</author>
1564
+ # <year>2005</year>
1565
+ # <price>30.00</price>
1566
+ # </book>
1567
+ # <book category="children">
1568
+ # <title lang="en">Harry Potter</title>
1569
+ # <author>J K. Rowling</author>
1570
+ # <year>2005</year>
1571
+ # <price>29.99</price>
1572
+ # </book>
1573
+ # <book category="web">
1574
+ # <title lang="en">XQuery Kick Start</title>
1575
+ # <author>James McGovern</author>
1576
+ # <author>Per Bothner</author>
1577
+ # <author>Kurt Cagle</author>
1578
+ # <author>James Linn</author>
1579
+ # <author>Vaidyanathan Nagarajan</author>
1580
+ # <year>2003</year>
1581
+ # <price>49.99</price>
1582
+ # </book>
1583
+ # <book category="web" cover="paperback">
1584
+ # <title lang="en">Learning XML</title>
1585
+ # <author>Erik T. Ray</author>
1586
+ # <year>2003</year>
1587
+ # <price>39.95</price>
1588
+ # </book>
1589
+ # </bookstore>
1590
+ # EOT
1591
+ # d = REXML::Document.new(xml_string)
1592
+ # elements = d.root.elements
1593
+ # elements # => #<REXML::Elements @element=<bookstore> ... </>>
1594
+ #
1595
+ class Elements
1596
+ include Enumerable
1597
+ # :call-seq:
1598
+ # new(parent) -> new_elements_object
1599
+ #
1600
+ # Returns a new \Elements object with the given +parent+.
1601
+ # Does _not_ assign <tt>parent.elements = self</tt>:
1602
+ #
1603
+ # d = REXML::Document.new(xml_string)
1604
+ # eles = REXML::Elements.new(d.root)
1605
+ # eles # => #<REXML::Elements @element=<bookstore> ... </>>
1606
+ # eles == d.root.elements # => false
1607
+ #
1608
+ def initialize parent
1609
+ @element = parent
1610
+ end
1611
+
1612
+ # :call-seq:
1613
+ # parent
1614
+ #
1615
+ # Returns the parent element cited in creating the \Elements object.
1616
+ # This element is also the default starting point for searching
1617
+ # in the \Elements object.
1618
+ #
1619
+ # d = REXML::Document.new(xml_string)
1620
+ # elements = REXML::Elements.new(d.root)
1621
+ # elements.parent == d.root # => true
1622
+ #
1623
+ def parent
1624
+ @element
1625
+ end
1626
+
1627
+ # :call-seq:
1628
+ # elements[index] -> element or nil
1629
+ # elements[xpath] -> element or nil
1630
+ # elements[n, name] -> element or nil
1631
+ #
1632
+ # Returns the first \Element object selected by the arguments,
1633
+ # if any found, or +nil+ if none found.
1634
+ #
1635
+ # Notes:
1636
+ # - The +index+ is 1-based, not 0-based, so that:
1637
+ # - The first element has index <tt>1</tt>
1638
+ # - The _nth_ element has index +n+.
1639
+ # - The selection ignores non-\Element nodes.
1640
+ #
1641
+ # When the single argument +index+ is given,
1642
+ # returns the element given by the index, if any; otherwise, +nil+:
1643
+ #
1644
+ # d = REXML::Document.new(xml_string)
1645
+ # eles = d.root.elements
1646
+ # eles # => #<REXML::Elements @element=<bookstore> ... </>>
1647
+ # eles[1] # => <book category='cooking'> ... </>
1648
+ # eles.size # => 4
1649
+ # eles[4] # => <book category='web' cover='paperback'> ... </>
1650
+ # eles[5] # => nil
1651
+ #
1652
+ # The node at this index is not an \Element, and so is not returned:
1653
+ #
1654
+ # eles = d.root.first.first # => <title lang='en'> ... </>
1655
+ # eles.to_a # => ["Everyday Italian"]
1656
+ # eles[1] # => nil
1657
+ #
1658
+ # When the single argument +xpath+ is given,
1659
+ # returns the first element found via that +xpath+, if any; otherwise, +nil+:
1660
+ #
1661
+ # eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
1662
+ # eles['/bookstore'] # => <bookstore> ... </>
1663
+ # eles['//book'] # => <book category='cooking'> ... </>
1664
+ # eles['//book [@category="children"]'] # => <book category='children'> ... </>
1665
+ # eles['/nosuch'] # => nil
1666
+ # eles['//nosuch'] # => nil
1667
+ # eles['//book [@category="nosuch"]'] # => nil
1668
+ # eles['.'] # => <bookstore> ... </>
1669
+ # eles['..'].class # => REXML::Document
1670
+ #
1671
+ # With arguments +n+ and +name+ given,
1672
+ # returns the _nth_ found element that has the given +name+,
1673
+ # or +nil+ if there is no such _nth_ element:
1674
+ #
1675
+ # eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
1676
+ # eles[1, 'book'] # => <book category='cooking'> ... </>
1677
+ # eles[4, 'book'] # => <book category='web' cover='paperback'> ... </>
1678
+ # eles[5, 'book'] # => nil
1679
+ #
1680
+ def []( index, name=nil)
1681
+ if index.kind_of? Integer
1682
+ raise "index (#{index}) must be >= 1" if index < 1
1683
+ name = literalize(name) if name
1684
+ num = 0
1685
+ @element.find { |child|
1686
+ child.kind_of? Element and
1687
+ (name.nil? ? true : child.has_name?( name )) and
1688
+ (num += 1) == index
1689
+ }
1690
+ else
1691
+ return XPath::first( @element, index )
1692
+ #{ |element|
1693
+ # return element if element.kind_of? Element
1694
+ #}
1695
+ #return nil
1696
+ end
1697
+ end
1698
+
1699
+ # :call-seq:
1700
+ # elements[] = index, replacement_element -> replacement_element or nil
1701
+ #
1702
+ # Replaces or adds an element.
1703
+ #
1704
+ # When <tt>eles[index]</tt> exists, replaces it with +replacement_element+
1705
+ # and returns +replacement_element+:
1706
+ #
1707
+ # d = REXML::Document.new(xml_string)
1708
+ # eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
1709
+ # eles[1] # => <book category='cooking'> ... </>
1710
+ # eles[1] = REXML::Element.new('foo')
1711
+ # eles[1] # => <foo/>
1712
+ #
1713
+ # Does nothing (or raises an exception)
1714
+ # if +replacement_element+ is not an \Element:
1715
+ # eles[2] # => <book category='web' cover='paperback'> ... </>
1716
+ # eles[2] = REXML::Text.new('bar')
1717
+ # eles[2] # => <book category='web' cover='paperback'> ... </>
1718
+ #
1719
+ # When <tt>eles[index]</tt> does not exist,
1720
+ # adds +replacement_element+ to the element and returns
1721
+ #
1722
+ # d = REXML::Document.new(xml_string)
1723
+ # eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
1724
+ # eles.size # => 4
1725
+ # eles[50] = REXML::Element.new('foo') # => <foo/>
1726
+ # eles.size # => 5
1727
+ # eles[5] # => <foo/>
1728
+ #
1729
+ # Does nothing (or raises an exception)
1730
+ # if +replacement_element+ is not an \Element:
1731
+ #
1732
+ # eles[50] = REXML::Text.new('bar') # => "bar"
1733
+ # eles.size # => 5
1734
+ #
1735
+ def []=( index, element )
1736
+ previous = self[index]
1737
+ if previous.nil?
1738
+ @element.add element
1739
+ else
1740
+ previous.replace_with element
1741
+ end
1742
+ return previous
1743
+ end
1744
+
1745
+ # :call-seq:
1746
+ # empty? -> true or false
1747
+ #
1748
+ # Returns +true+ if there are no children, +false+ otherwise.
1749
+ #
1750
+ # d = REXML::Document.new('')
1751
+ # d.elements.empty? # => true
1752
+ # d = REXML::Document.new(xml_string)
1753
+ # d.elements.empty? # => false
1754
+ #
1755
+ def empty?
1756
+ @element.find{ |child| child.kind_of? Element}.nil?
1757
+ end
1758
+
1759
+ # :call-seq:
1760
+ # index(element)
1761
+ #
1762
+ # Returns the 1-based index of the given +element+, if found;
1763
+ # otherwise, returns -1:
1764
+ #
1765
+ # d = REXML::Document.new(xml_string)
1766
+ # elements = d.root.elements
1767
+ # ele_1, ele_2, ele_3, ele_4 = *elements
1768
+ # elements.index(ele_4) # => 4
1769
+ # elements.delete(ele_3)
1770
+ # elements.index(ele_4) # => 3
1771
+ # elements.index(ele_3) # => -1
1772
+ #
1773
+ def index element
1774
+ rv = 0
1775
+ found = @element.find do |child|
1776
+ child.kind_of? Element and
1777
+ (rv += 1) and
1778
+ child == element
1779
+ end
1780
+ return rv if found == element
1781
+ return -1
1782
+ end
1783
+
1784
+ # :call-seq:
1785
+ # delete(index) -> removed_element or nil
1786
+ # delete(element) -> removed_element or nil
1787
+ # delete(xpath) -> removed_element or nil
1788
+ #
1789
+ # Removes an element; returns the removed element, or +nil+ if none removed.
1790
+ #
1791
+ # With integer argument +index+ given,
1792
+ # removes the child element at that offset:
1793
+ #
1794
+ # d = REXML::Document.new(xml_string)
1795
+ # elements = d.root.elements
1796
+ # elements.size # => 4
1797
+ # elements[2] # => <book category='children'> ... </>
1798
+ # elements.delete(2) # => <book category='children'> ... </>
1799
+ # elements.size # => 3
1800
+ # elements[2] # => <book category='web'> ... </>
1801
+ # elements.delete(50) # => nil
1802
+ #
1803
+ # With element argument +element+ given,
1804
+ # removes that child element:
1805
+ #
1806
+ # d = REXML::Document.new(xml_string)
1807
+ # elements = d.root.elements
1808
+ # ele_1, ele_2, ele_3, ele_4 = *elements
1809
+ # elements.size # => 4
1810
+ # elements[2] # => <book category='children'> ... </>
1811
+ # elements.delete(ele_2) # => <book category='children'> ... </>
1812
+ # elements.size # => 3
1813
+ # elements[2] # => <book category='web'> ... </>
1814
+ # elements.delete(ele_2) # => nil
1815
+ #
1816
+ # With string argument +xpath+ given,
1817
+ # removes the first element found via that xpath:
1818
+ #
1819
+ # d = REXML::Document.new(xml_string)
1820
+ # elements = d.root.elements
1821
+ # elements.delete('//book') # => <book category='cooking'> ... </>
1822
+ # elements.delete('//book [@category="children"]') # => <book category='children'> ... </>
1823
+ # elements.delete('//nosuch') # => nil
1824
+ #
1825
+ def delete element
1826
+ if element.kind_of? Element
1827
+ @element.delete element
1828
+ else
1829
+ el = self[element]
1830
+ el.remove if el
1831
+ end
1832
+ end
1833
+
1834
+ # :call-seq:
1835
+ # delete_all(xpath)
1836
+ #
1837
+ # Removes all elements found via the given +xpath+;
1838
+ # returns the array of removed elements, if any, else +nil+.
1839
+ #
1840
+ # d = REXML::Document.new(xml_string)
1841
+ # elements = d.root.elements
1842
+ # elements.size # => 4
1843
+ # deleted_elements = elements.delete_all('//book [@category="web"]')
1844
+ # deleted_elements.size # => 2
1845
+ # elements.size # => 2
1846
+ # deleted_elements = elements.delete_all('//book')
1847
+ # deleted_elements.size # => 2
1848
+ # elements.size # => 0
1849
+ # elements.delete_all('//book') # => []
1850
+ #
1851
+ def delete_all( xpath )
1852
+ rv = []
1853
+ XPath::each( @element, xpath) {|element|
1854
+ rv << element if element.kind_of? Element
1855
+ }
1856
+ rv.each do |element|
1857
+ @element.delete element
1858
+ element.remove
1859
+ end
1860
+ return rv
1861
+ end
1862
+
1863
+ # :call-seq:
1864
+ # add -> new_element
1865
+ # add(name) -> new_element
1866
+ # add(element) -> element
1867
+ #
1868
+ # Adds an element; returns the element added.
1869
+ #
1870
+ # With no argument, creates and adds a new element.
1871
+ # The new element has:
1872
+ #
1873
+ # - No name.
1874
+ # - \Parent from the \Elements object.
1875
+ # - Context from the that parent.
1876
+ #
1877
+ # Example:
1878
+ #
1879
+ # d = REXML::Document.new(xml_string)
1880
+ # elements = d.root.elements
1881
+ # parent = elements.parent # => <bookstore> ... </>
1882
+ # parent.context = {raw: :all}
1883
+ # elements.size # => 4
1884
+ # new_element = elements.add # => </>
1885
+ # elements.size # => 5
1886
+ # new_element.name # => nil
1887
+ # new_element.parent # => <bookstore> ... </>
1888
+ # new_element.context # => {:raw=>:all}
1889
+ #
1890
+ # With string argument +name+, creates and adds a new element.
1891
+ # The new element has:
1892
+ #
1893
+ # - Name +name+.
1894
+ # - \Parent from the \Elements object.
1895
+ # - Context from the that parent.
1896
+ #
1897
+ # Example:
1898
+ #
1899
+ # d = REXML::Document.new(xml_string)
1900
+ # elements = d.root.elements
1901
+ # parent = elements.parent # => <bookstore> ... </>
1902
+ # parent.context = {raw: :all}
1903
+ # elements.size # => 4
1904
+ # new_element = elements.add('foo') # => <foo/>
1905
+ # elements.size # => 5
1906
+ # new_element.name # => "foo"
1907
+ # new_element.parent # => <bookstore> ... </>
1908
+ # new_element.context # => {:raw=>:all}
1909
+ #
1910
+ # With argument +element+,
1911
+ # creates and adds a clone of the given +element+.
1912
+ # The new element has name, parent, and context from the given +element+.
1913
+ #
1914
+ # d = REXML::Document.new(xml_string)
1915
+ # elements = d.root.elements
1916
+ # elements.size # => 4
1917
+ # e0 = REXML::Element.new('foo')
1918
+ # e1 = REXML::Element.new('bar', e0, {raw: :all})
1919
+ # element = elements.add(e1) # => <bar/>
1920
+ # elements.size # => 5
1921
+ # element.name # => "bar"
1922
+ # element.parent # => <bookstore> ... </>
1923
+ # element.context # => {:raw=>:all}
1924
+ #
1925
+ def add element=nil
1926
+ if element.nil?
1927
+ Element.new("", self, @element.context)
1928
+ elsif not element.kind_of?(Element)
1929
+ Element.new(element, self, @element.context)
1930
+ else
1931
+ @element << element
1932
+ element.context = @element.context
1933
+ element
1934
+ end
1935
+ end
1936
+
1937
+ alias :<< :add
1938
+
1939
+ # :call-seq:
1940
+ # each(xpath = nil) {|element| ... } -> self
1941
+ #
1942
+ # Iterates over the elements.
1943
+ #
1944
+ # With no argument, calls the block with each element:
1945
+ #
1946
+ # d = REXML::Document.new(xml_string)
1947
+ # elements = d.root.elements
1948
+ # elements.each {|element| p element }
1949
+ #
1950
+ # Output:
1951
+ #
1952
+ # <book category='cooking'> ... </>
1953
+ # <book category='children'> ... </>
1954
+ # <book category='web'> ... </>
1955
+ # <book category='web' cover='paperback'> ... </>
1956
+ #
1957
+ # With argument +xpath+, calls the block with each element
1958
+ # that matches the given +xpath+:
1959
+ #
1960
+ # elements.each('//book [@category="web"]') {|element| p element }
1961
+ #
1962
+ # Output:
1963
+ #
1964
+ # <book category='web'> ... </>
1965
+ # <book category='web' cover='paperback'> ... </>
1966
+ #
1967
+ def each( xpath=nil )
1968
+ XPath::each( @element, xpath ) {|e| yield e if e.kind_of? Element }
1969
+ end
1970
+
1971
+ # :call-seq:
1972
+ # collect(xpath = nil) {|element| ... } -> array
1973
+ #
1974
+ # Iterates over the elements; returns the array of block return values.
1975
+ #
1976
+ # With no argument, iterates over all elements:
1977
+ #
1978
+ # d = REXML::Document.new(xml_string)
1979
+ # elements = d.root.elements
1980
+ # elements.collect {|element| element.size } # => [9, 9, 17, 9]
1981
+ #
1982
+ # With argument +xpath+, iterates over elements that match
1983
+ # the given +xpath+:
1984
+ #
1985
+ # xpath = '//book [@category="web"]'
1986
+ # elements.collect(xpath) {|element| element.size } # => [17, 9]
1987
+ #
1988
+ def collect( xpath=nil )
1989
+ collection = []
1990
+ XPath::each( @element, xpath ) {|e|
1991
+ collection << yield(e) if e.kind_of?(Element)
1992
+ }
1993
+ collection
1994
+ end
1995
+
1996
+ # :call-seq:
1997
+ # inject(xpath = nil, initial = nil) -> object
1998
+ #
1999
+ # Calls the block with elements; returns the last block return value.
2000
+ #
2001
+ # With no argument, iterates over the elements, calling the block
2002
+ # <tt>elements.size - 1</tt> times.
2003
+ #
2004
+ # - The first call passes the first and second elements.
2005
+ # - The second call passes the first block return value and the third element.
2006
+ # - The third call passes the second block return value and the fourth element.
2007
+ # - And so on.
2008
+ #
2009
+ # In this example, the block returns the passed element,
2010
+ # which is then the object argument to the next call:
2011
+ #
2012
+ # d = REXML::Document.new(xml_string)
2013
+ # elements = d.root.elements
2014
+ # elements.inject do |object, element|
2015
+ # p [elements.index(object), elements.index(element)]
2016
+ # element
2017
+ # end
2018
+ #
2019
+ # Output:
2020
+ #
2021
+ # [1, 2]
2022
+ # [2, 3]
2023
+ # [3, 4]
2024
+ #
2025
+ # With the single argument +xpath+, calls the block only with
2026
+ # elements matching that xpath:
2027
+ #
2028
+ # elements.inject('//book [@category="web"]') do |object, element|
2029
+ # p [elements.index(object), elements.index(element)]
2030
+ # element
2031
+ # end
2032
+ #
2033
+ # Output:
2034
+ #
2035
+ # [3, 4]
2036
+ #
2037
+ # With argument +xpath+ given as +nil+
2038
+ # and argument +initial+ also given,
2039
+ # calls the block once for each element.
2040
+ #
2041
+ # - The first call passes the +initial+ and the first element.
2042
+ # - The second call passes the first block return value and the second element.
2043
+ # - The third call passes the second block return value and the third element.
2044
+ # - And so on.
2045
+ #
2046
+ # In this example, the first object index is <tt>-1</tt>
2047
+ #
2048
+ # elements.inject(nil, 'Initial') do |object, element|
2049
+ # p [elements.index(object), elements.index(element)]
2050
+ # element
2051
+ # end
2052
+ #
2053
+ # Output:
2054
+ #
2055
+ # [-1, 1]
2056
+ # [1, 2]
2057
+ # [2, 3]
2058
+ # [3, 4]
2059
+ #
2060
+ # In this form the passed object can be used as an accumulator:
2061
+ #
2062
+ # elements.inject(nil, 0) do |total, element|
2063
+ # total += element.size
2064
+ # end # => 44
2065
+ #
2066
+ # With both arguments +xpath+ and +initial+ are given,
2067
+ # calls the block only with elements matching that xpath:
2068
+ #
2069
+ # elements.inject('//book [@category="web"]', 0) do |total, element|
2070
+ # total += element.size
2071
+ # end # => 26
2072
+ #
2073
+ def inject( xpath=nil, initial=nil )
2074
+ first = true
2075
+ XPath::each( @element, xpath ) {|e|
2076
+ if (e.kind_of? Element)
2077
+ if (first and initial == nil)
2078
+ initial = e
2079
+ first = false
2080
+ else
2081
+ initial = yield( initial, e ) if e.kind_of? Element
2082
+ end
2083
+ end
2084
+ }
2085
+ initial
2086
+ end
2087
+
2088
+ # :call-seq:
2089
+ # size -> integer
2090
+ #
2091
+ # Returns the count of \Element children:
2092
+ #
2093
+ # d = REXML::Document.new '<a>sean<b/>elliott<b/>russell<b/></a>'
2094
+ # d.root.elements.size # => 3 # Three elements.
2095
+ # d.root.size # => 6 # Three elements plus three text nodes..
2096
+ #
2097
+ def size
2098
+ count = 0
2099
+ @element.each {|child| count+=1 if child.kind_of? Element }
2100
+ count
2101
+ end
2102
+
2103
+ # :call-seq:
2104
+ # to_a(xpath = nil) -> array_of_elements
2105
+ #
2106
+ # Returns an array of element children (not including non-element children).
2107
+ #
2108
+ # With no argument, returns an array of all element children:
2109
+ #
2110
+ # d = REXML::Document.new '<a>sean<b/>elliott<c/></a>'
2111
+ # elements = d.root.elements
2112
+ # elements.to_a # => [<b/>, <c/>] # Omits non-element children.
2113
+ # children = d.root.children
2114
+ # children # => ["sean", <b/>, "elliott", <c/>] # Includes non-element children.
2115
+ #
2116
+ # With argument +xpath+, returns an array of element children
2117
+ # that match the xpath:
2118
+ #
2119
+ # elements.to_a('//c') # => [<c/>]
2120
+ #
2121
+ def to_a( xpath=nil )
2122
+ rv = XPath.match( @element, xpath )
2123
+ return rv.find_all{|e| e.kind_of? Element} if xpath
2124
+ rv
2125
+ end
2126
+
2127
+ private
2128
+ # Private helper class. Removes quotes from quoted strings
2129
+ def literalize name
2130
+ name = name[1..-2] if name[0] == ?' or name[0] == ?" #'
2131
+ name
2132
+ end
2133
+ end
2134
+
2135
+ ########################################################################
2136
+ # ATTRIBUTES #
2137
+ ########################################################################
2138
+
2139
+ # A class that defines the set of Attributes of an Element and provides
2140
+ # operations for accessing elements in that set.
2141
+ class Attributes < Hash
2142
+
2143
+ # :call-seq:
2144
+ # new(element)
2145
+ #
2146
+ # Creates and returns a new \REXML::Attributes object.
2147
+ # The element given by argument +element+ is stored,
2148
+ # but its own attributes are not modified:
2149
+ #
2150
+ # ele = REXML::Element.new('foo')
2151
+ # attrs = REXML::Attributes.new(ele)
2152
+ # attrs.object_id == ele.attributes.object_id # => false
2153
+ #
2154
+ # Other instance methods in class \REXML::Attributes may refer to:
2155
+ #
2156
+ # - +element.document+.
2157
+ # - +element.prefix+.
2158
+ # - +element.expanded_name+.
2159
+ #
2160
+ def initialize element
2161
+ @element = element
2162
+ end
2163
+
2164
+ # :call-seq:
2165
+ # [name] -> attribute_value or nil
2166
+ #
2167
+ # Returns the value for the attribute given by +name+,
2168
+ # if it exists; otherwise +nil+.
2169
+ # The value returned is the unnormalized attribute value,
2170
+ # with entities expanded:
2171
+ #
2172
+ # xml_string = <<-EOT
2173
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2174
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2175
+ # </root>
2176
+ # EOT
2177
+ # d = REXML::Document.new(xml_string)
2178
+ # ele = d.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2179
+ # ele.attributes['att'] # => "<"
2180
+ # ele.attributes['bar:att'] # => "2"
2181
+ # ele.attributes['nosuch'] # => nil
2182
+ #
2183
+ # Related: get_attribute (returns an \Attribute object).
2184
+ #
2185
+ def [](name)
2186
+ attr = get_attribute(name)
2187
+ return attr.value unless attr.nil?
2188
+ return nil
2189
+ end
2190
+
2191
+ # :call-seq:
2192
+ # to_a -> array_of_attribute_objects
2193
+ #
2194
+ # Returns an array of \REXML::Attribute objects representing
2195
+ # the attributes:
2196
+ #
2197
+ # xml_string = <<-EOT
2198
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2199
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2200
+ # </root>
2201
+ # EOT
2202
+ # d = REXML::Document.new(xml_string)
2203
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2204
+ # attrs = ele.attributes.to_a # => [foo:att='1', bar:att='2', att='&lt;']
2205
+ # attrs.first.class # => REXML::Attribute
2206
+ #
2207
+ def to_a
2208
+ enum_for(:each_attribute).to_a
2209
+ end
2210
+
2211
+ # :call-seq:
2212
+ # length
2213
+ #
2214
+ # Returns the count of attributes:
2215
+ #
2216
+ # xml_string = <<-EOT
2217
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2218
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2219
+ # </root>
2220
+ # EOT
2221
+ # d = REXML::Document.new(xml_string)
2222
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2223
+ # ele.attributes.length # => 3
2224
+ #
2225
+ def length
2226
+ c = 0
2227
+ each_attribute { c+=1 }
2228
+ c
2229
+ end
2230
+ alias :size :length
2231
+
2232
+ # :call-seq:
2233
+ # each_attribute {|attr| ... }
2234
+ #
2235
+ # Calls the given block with each \REXML::Attribute object:
2236
+ #
2237
+ # xml_string = <<-EOT
2238
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2239
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2240
+ # </root>
2241
+ # EOT
2242
+ # d = REXML::Document.new(xml_string)
2243
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2244
+ # ele.attributes.each_attribute do |attr|
2245
+ # p [attr.class, attr]
2246
+ # end
2247
+ #
2248
+ # Output:
2249
+ #
2250
+ # [REXML::Attribute, foo:att='1']
2251
+ # [REXML::Attribute, bar:att='2']
2252
+ # [REXML::Attribute, att='&lt;']
2253
+ #
2254
+ def each_attribute # :yields: attribute
2255
+ return to_enum(__method__) unless block_given?
2256
+ each_value do |val|
2257
+ if val.kind_of? Attribute
2258
+ yield val
2259
+ else
2260
+ val.each_value { |atr| yield atr }
2261
+ end
2262
+ end
2263
+ end
2264
+
2265
+ # :call-seq:
2266
+ # each {|expanded_name, value| ... }
2267
+ #
2268
+ # Calls the given block with each expanded-name/value pair:
2269
+ #
2270
+ # xml_string = <<-EOT
2271
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2272
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2273
+ # </root>
2274
+ # EOT
2275
+ # d = REXML::Document.new(xml_string)
2276
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2277
+ # ele.attributes.each do |expanded_name, value|
2278
+ # p [expanded_name, value]
2279
+ # end
2280
+ #
2281
+ # Output:
2282
+ #
2283
+ # ["foo:att", "1"]
2284
+ # ["bar:att", "2"]
2285
+ # ["att", "<"]
2286
+ #
2287
+ def each
2288
+ return to_enum(__method__) unless block_given?
2289
+ each_attribute do |attr|
2290
+ yield [attr.expanded_name, attr.value]
2291
+ end
2292
+ end
2293
+
2294
+ # :call-seq:
2295
+ # get_attribute(name) -> attribute_object or nil
2296
+ #
2297
+ # Returns the \REXML::Attribute object for the given +name+:
2298
+ #
2299
+ # xml_string = <<-EOT
2300
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2301
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2302
+ # </root>
2303
+ # EOT
2304
+ # d = REXML::Document.new(xml_string)
2305
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2306
+ # attrs = ele.attributes
2307
+ # attrs.get_attribute('foo:att') # => foo:att='1'
2308
+ # attrs.get_attribute('foo:att').class # => REXML::Attribute
2309
+ # attrs.get_attribute('bar:att') # => bar:att='2'
2310
+ # attrs.get_attribute('att') # => att='&lt;'
2311
+ # attrs.get_attribute('nosuch') # => nil
2312
+ #
2313
+ def get_attribute( name )
2314
+ attr = fetch( name, nil )
2315
+ if attr.nil?
2316
+ return nil if name.nil?
2317
+ # Look for prefix
2318
+ name =~ Namespace::NAMESPLIT
2319
+ prefix, n = $1, $2
2320
+ if prefix
2321
+ attr = fetch( n, nil )
2322
+ # check prefix
2323
+ if attr == nil
2324
+ elsif attr.kind_of? Attribute
2325
+ return attr if prefix == attr.prefix
2326
+ else
2327
+ attr = attr[ prefix ]
2328
+ return attr
2329
+ end
2330
+ end
2331
+ element_document = @element.document
2332
+ if element_document and element_document.doctype
2333
+ expn = @element.expanded_name
2334
+ expn = element_document.doctype.name if expn.size == 0
2335
+ attr_val = element_document.doctype.attribute_of(expn, name)
2336
+ return Attribute.new( name, attr_val ) if attr_val
2337
+ end
2338
+ return nil
2339
+ end
2340
+ if attr.kind_of? Hash
2341
+ attr = attr[ @element.prefix ]
2342
+ end
2343
+ return attr
2344
+ end
2345
+
2346
+ # :call-seq:
2347
+ # [name] = value -> value
2348
+ #
2349
+ # When +value+ is non-+nil+,
2350
+ # assigns that to the attribute for the given +name+,
2351
+ # overwriting the previous value if it exists:
2352
+ #
2353
+ # xml_string = <<-EOT
2354
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2355
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2356
+ # </root>
2357
+ # EOT
2358
+ # d = REXML::Document.new(xml_string)
2359
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2360
+ # attrs = ele.attributes
2361
+ # attrs['foo:att'] = '2' # => "2"
2362
+ # attrs['baz:att'] = '3' # => "3"
2363
+ #
2364
+ # When +value+ is +nil+, deletes the attribute if it exists:
2365
+ #
2366
+ # attrs['baz:att'] = nil
2367
+ # attrs.include?('baz:att') # => false
2368
+ #
2369
+ def []=( name, value )
2370
+ if value.nil? # Delete the named attribute
2371
+ attr = get_attribute(name)
2372
+ delete attr
2373
+ return
2374
+ end
2375
+
2376
+ unless value.kind_of? Attribute
2377
+ if @element.document and @element.document.doctype
2378
+ value = Text::normalize( value, @element.document.doctype )
2379
+ else
2380
+ value = Text::normalize( value, nil )
2381
+ end
2382
+ value = Attribute.new(name, value)
2383
+ end
2384
+ value.element = @element
2385
+ old_attr = fetch(value.name, nil)
2386
+ if old_attr.nil?
2387
+ store(value.name, value)
2388
+ elsif old_attr.kind_of? Hash
2389
+ old_attr[value.prefix] = value
2390
+ elsif old_attr.prefix != value.prefix
2391
+ # Check for conflicting namespaces
2392
+ if value.prefix != "xmlns" and old_attr.prefix != "xmlns"
2393
+ old_namespace = old_attr.namespace
2394
+ new_namespace = value.namespace
2395
+ if old_namespace == new_namespace
2396
+ raise ParseException.new(
2397
+ "Namespace conflict in adding attribute \"#{value.name}\": "+
2398
+ "Prefix \"#{old_attr.prefix}\" = \"#{old_namespace}\" and "+
2399
+ "prefix \"#{value.prefix}\" = \"#{new_namespace}\"")
2400
+ end
2401
+ end
2402
+ store value.name, {old_attr.prefix => old_attr,
2403
+ value.prefix => value}
2404
+ else
2405
+ store value.name, value
2406
+ end
2407
+ return @element
2408
+ end
2409
+
2410
+ # :call-seq:
2411
+ # prefixes -> array_of_prefix_strings
2412
+ #
2413
+ # Returns an array of prefix strings in the attributes.
2414
+ # The array does not include the default
2415
+ # namespace declaration, if one exists.
2416
+ #
2417
+ # xml_string = '<a xmlns="foo" xmlns:x="bar" xmlns:y="twee" z="glorp"/>'
2418
+ # d = REXML::Document.new(xml_string)
2419
+ # d.root.attributes.prefixes # => ["x", "y"]
2420
+ #
2421
+ def prefixes
2422
+ ns = []
2423
+ each_attribute do |attribute|
2424
+ ns << attribute.name if attribute.prefix == 'xmlns'
2425
+ end
2426
+ if @element.document and @element.document.doctype
2427
+ expn = @element.expanded_name
2428
+ expn = @element.document.doctype.name if expn.size == 0
2429
+ @element.document.doctype.attributes_of(expn).each {
2430
+ |attribute|
2431
+ ns << attribute.name if attribute.prefix == 'xmlns'
2432
+ }
2433
+ end
2434
+ ns
2435
+ end
2436
+
2437
+ # :call-seq:
2438
+ # namespaces
2439
+ #
2440
+ # Returns a hash of name/value pairs for the namespaces:
2441
+ #
2442
+ # xml_string = '<a xmlns="foo" xmlns:x="bar" xmlns:y="twee" z="glorp"/>'
2443
+ # d = REXML::Document.new(xml_string)
2444
+ # d.root.attributes.namespaces # => {"xmlns"=>"foo", "x"=>"bar", "y"=>"twee"}
2445
+ #
2446
+ def namespaces
2447
+ namespaces = {}
2448
+ each_attribute do |attribute|
2449
+ namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
2450
+ end
2451
+ if @element.document and @element.document.doctype
2452
+ expn = @element.expanded_name
2453
+ expn = @element.document.doctype.name if expn.size == 0
2454
+ @element.document.doctype.attributes_of(expn).each {
2455
+ |attribute|
2456
+ namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
2457
+ }
2458
+ end
2459
+ namespaces
2460
+ end
2461
+
2462
+ # :call-seq:
2463
+ # delete(name) -> element
2464
+ # delete(attribute) -> element
2465
+ #
2466
+ # Removes a specified attribute if it exists;
2467
+ # returns the attributes' element.
2468
+ #
2469
+ # When string argument +name+ is given,
2470
+ # removes the attribute of that name if it exists:
2471
+ #
2472
+ # xml_string = <<-EOT
2473
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2474
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2475
+ # </root>
2476
+ # EOT
2477
+ # d = REXML::Document.new(xml_string)
2478
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2479
+ # attrs = ele.attributes
2480
+ # attrs.delete('foo:att') # => <ele bar:att='2' att='&lt;'/>
2481
+ # attrs.delete('foo:att') # => <ele bar:att='2' att='&lt;'/>
2482
+ #
2483
+ # When attribute argument +attribute+ is given,
2484
+ # removes that attribute if it exists:
2485
+ #
2486
+ # attr = REXML::Attribute.new('bar:att', '2')
2487
+ # attrs.delete(attr) # => <ele att='&lt;'/> # => <ele att='&lt;'/>
2488
+ # attrs.delete(attr) # => <ele att='&lt;'/> # => <ele/>
2489
+ #
2490
+ def delete( attribute )
2491
+ name = nil
2492
+ prefix = nil
2493
+ if attribute.kind_of? Attribute
2494
+ name = attribute.name
2495
+ prefix = attribute.prefix
2496
+ else
2497
+ attribute =~ Namespace::NAMESPLIT
2498
+ prefix, name = $1, $2
2499
+ prefix = '' unless prefix
2500
+ end
2501
+ old = fetch(name, nil)
2502
+ if old.kind_of? Hash # the supplied attribute is one of many
2503
+ old.delete(prefix)
2504
+ if old.size == 1
2505
+ repl = nil
2506
+ old.each_value{|v| repl = v}
2507
+ store name, repl
2508
+ end
2509
+ elsif old.nil?
2510
+ return @element
2511
+ else # the supplied attribute is a top-level one
2512
+ super(name)
2513
+ end
2514
+ @element
2515
+ end
2516
+
2517
+ # :call-seq:
2518
+ # add(attribute) -> attribute
2519
+ #
2520
+ # Adds attribute +attribute+, replacing the previous
2521
+ # attribute of the same name if it exists;
2522
+ # returns +attribute+:
2523
+ #
2524
+ # xml_string = <<-EOT
2525
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2526
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2527
+ # </root>
2528
+ # EOT
2529
+ # d = REXML::Document.new(xml_string)
2530
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2531
+ # attrs = ele.attributes
2532
+ # attrs # => {"att"=>{"foo"=>foo:att='1', "bar"=>bar:att='2', ""=>att='&lt;'}}
2533
+ # attrs.add(REXML::Attribute.new('foo:att', '2')) # => foo:att='2'
2534
+ # attrs.add(REXML::Attribute.new('baz', '3')) # => baz='3'
2535
+ # attrs.include?('baz') # => true
2536
+ #
2537
+ def add( attribute )
2538
+ self[attribute.name] = attribute
2539
+ end
2540
+
2541
+ alias :<< :add
2542
+
2543
+ # :call-seq:
2544
+ # delete_all(name) -> array_of_removed_attributes
2545
+ #
2546
+ # Removes all attributes matching the given +name+;
2547
+ # returns an array of the removed attributes:
2548
+ #
2549
+ # xml_string = <<-EOT
2550
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2551
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2552
+ # </root>
2553
+ # EOT
2554
+ # d = REXML::Document.new(xml_string)
2555
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2556
+ # attrs = ele.attributes
2557
+ # attrs.delete_all('att') # => [att='&lt;']
2558
+ #
2559
+ def delete_all( name )
2560
+ rv = []
2561
+ each_attribute { |attribute|
2562
+ rv << attribute if attribute.expanded_name == name
2563
+ }
2564
+ rv.each{ |attr| attr.remove }
2565
+ return rv
2566
+ end
2567
+
2568
+ # :call-seq:
2569
+ # get_attribute_ns(namespace, name)
2570
+ #
2571
+ # Returns the \REXML::Attribute object among the attributes
2572
+ # that matches the given +namespace+ and +name+:
2573
+ #
2574
+ # xml_string = <<-EOT
2575
+ # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2576
+ # <ele foo:att='1' bar:att='2' att='&lt;'/>
2577
+ # </root>
2578
+ # EOT
2579
+ # d = REXML::Document.new(xml_string)
2580
+ # ele = d.root.elements['//ele'] # => <a foo:att='1' bar:att='2' att='&lt;'/>
2581
+ # attrs = ele.attributes
2582
+ # attrs.get_attribute_ns('http://foo', 'att') # => foo:att='1'
2583
+ # attrs.get_attribute_ns('http://foo', 'nosuch') # => nil
2584
+ #
2585
+ def get_attribute_ns(namespace, name)
2586
+ result = nil
2587
+ each_attribute() { |attribute|
2588
+ if name == attribute.name &&
2589
+ namespace == attribute.namespace() &&
2590
+ ( !namespace.empty? || !attribute.fully_expanded_name.index(':') )
2591
+ # foo will match xmlns:foo, but only if foo isn't also an attribute
2592
+ result = attribute if !result or !namespace.empty? or
2593
+ !attribute.fully_expanded_name.index(':')
2594
+ end
2595
+ }
2596
+ result
2597
+ end
2598
+ end
2599
+ end