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.
- checksums.yaml +4 -4
- data/CHANGES.md +46 -0
- data/README.md +11 -2
- data/bundle/load.rb +5 -3
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/CHANGELOG.md +16 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/FAQ.md +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/Gemfile +1 -4
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/MIT-LICENSE +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/README.md +2 -3
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/REFERENCE.md +29 -7
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/TODO +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/haml.gemspec +2 -1
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/attribute_builder.rb +3 -3
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/attribute_compiler.rb +42 -31
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/attribute_parser.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/buffer.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/compiler.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/engine.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/error.rb +0 -0
- data/bundle/ruby/2.7.0/gems/haml-5.2.1/lib/haml/escapable.rb +77 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/exec.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/filters.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/generator.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers.rb +7 -1
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/action_view_extensions.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/action_view_mods.rb +0 -0
- 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
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/safe_erubi_template.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/safe_erubis_template.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/helpers/xss_mods.rb +6 -3
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/options.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/parser.rb +32 -4
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/plugin.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/railtie.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/sass_rails_filter.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/template.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/template/options.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/temple_engine.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/temple_line_counter.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/util.rb +1 -1
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/lib/haml/version.rb +1 -1
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/yard/default/fulldoc/html/css/common.sass +0 -0
- data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.1}/yard/default/layout/html/footer.erb +0 -0
- data/bundle/ruby/2.7.0/gems/parallel-1.20.1/MIT-LICENSE.txt +20 -0
- data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel.rb +523 -0
- data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/processor_count.rb +42 -0
- data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/version.rb +3 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/LICENSE.txt +22 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/NEWS.md +178 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/README.md +48 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml.rb +3 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/attlistdecl.rb +63 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/attribute.rb +205 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/cdata.rb +68 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/child.rb +97 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/comment.rb +80 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/doctype.rb +311 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/document.rb +451 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/attlistdecl.rb +11 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/dtd.rb +47 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/elementdecl.rb +18 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/entitydecl.rb +57 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/dtd/notationdecl.rb +40 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/element.rb +2599 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/encoding.rb +51 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/entity.rb +171 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/formatters/default.rb +116 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/formatters/pretty.rb +142 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/formatters/transitive.rb +58 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/functions.rb +447 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/instruction.rb +79 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/light/node.rb +188 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/namespace.rb +59 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/node.rb +76 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/output.rb +30 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parent.rb +166 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parseexception.rb +52 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/baseparser.rb +694 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/lightparser.rb +59 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/pullparser.rb +197 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/sax2parser.rb +273 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/streamparser.rb +61 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/treeparser.rb +101 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/ultralightparser.rb +57 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/parsers/xpathparser.rb +689 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/quickpath.rb +266 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/rexml.rb +37 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/sax2listener.rb +98 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/security.rb +28 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/source.rb +298 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/streamlistener.rb +93 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/text.rb +424 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/undefinednamespaceexception.rb +9 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/validation/relaxng.rb +539 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/validation/validation.rb +144 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/validation/validationexception.rb +10 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xmldecl.rb +130 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xmltokens.rb +85 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xpath.rb +81 -0
- data/bundle/ruby/2.7.0/gems/rexml-3.2.5/lib/rexml/xpath_parser.rb +974 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/History.rdoc +25 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/Manifest.txt +2 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/README.rdoc +0 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/compare/normalize.rb +2 -2
- data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/debugging.md +190 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/rp_extensions.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/rp_stringscanner.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby20_parser.rb +2392 -2384
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby20_parser.y +6 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby21_parser.rb +2553 -2550
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby21_parser.y +6 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby22_parser.rb +2491 -2471
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby22_parser.y +6 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby23_parser.rb +2422 -2403
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby23_parser.y +6 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby24_parser.rb +2460 -2450
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby24_parser.y +6 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby25_parser.rb +2450 -2441
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby25_parser.y +6 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby26_parser.rb +2444 -2433
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby26_parser.y +7 -1
- data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby27_parser.rb +7310 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby27_parser.y +21 -1
- data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.rb +7310 -0
- data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.y +2677 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_lexer.rb +19 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_lexer.rex +1 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_lexer.rex.rb +1 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_parser.rb +2 -0
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_parser.yy +27 -1
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/lib/ruby_parser_extras.rb +2 -2
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/tools/munge.rb +2 -2
- data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.16.0}/tools/ripper.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/History.rdoc +12 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/Manifest.txt +0 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/README.rdoc +0 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/composite_sexp_processor.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/pt_testcase.rb +2 -2
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/sexp.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/sexp_matcher.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/sexp_processor.rb +1 -1
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/strict_sexp.rb +0 -0
- data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.3}/lib/unique.rb +0 -0
- data/lib/brakeman.rb +21 -4
- data/lib/brakeman/app_tree.rb +36 -3
- data/lib/brakeman/checks/base_check.rb +7 -1
- data/lib/brakeman/checks/check_detailed_exceptions.rb +1 -1
- data/lib/brakeman/checks/check_evaluation.rb +1 -1
- data/lib/brakeman/checks/check_execute.rb +2 -1
- data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
- data/lib/brakeman/checks/check_regex_dos.rb +1 -1
- data/lib/brakeman/checks/check_sanitize_methods.rb +2 -1
- data/lib/brakeman/checks/check_sql.rb +16 -3
- data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
- data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
- data/lib/brakeman/file_parser.rb +50 -22
- data/lib/brakeman/options.rb +5 -1
- data/lib/brakeman/parsers/template_parser.rb +26 -3
- data/lib/brakeman/processors/alias_processor.rb +91 -19
- data/lib/brakeman/processors/base_processor.rb +4 -4
- data/lib/brakeman/processors/controller_alias_processor.rb +6 -43
- data/lib/brakeman/processors/controller_processor.rb +1 -1
- data/lib/brakeman/processors/haml_template_processor.rb +8 -1
- data/lib/brakeman/processors/lib/call_conversion_helper.rb +10 -0
- data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
- data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
- data/lib/brakeman/processors/library_processor.rb +9 -0
- data/lib/brakeman/processors/output_processor.rb +1 -1
- data/lib/brakeman/processors/template_alias_processor.rb +5 -0
- data/lib/brakeman/report.rb +12 -1
- data/lib/brakeman/report/ignore/interactive.rb +1 -1
- data/lib/brakeman/report/report_base.rb +0 -2
- data/lib/brakeman/report/report_csv.rb +37 -60
- data/lib/brakeman/report/report_github.rb +31 -0
- data/lib/brakeman/report/report_junit.rb +2 -2
- data/lib/brakeman/report/report_sarif.rb +1 -1
- data/lib/brakeman/report/report_sonar.rb +38 -0
- data/lib/brakeman/report/report_tabs.rb +1 -1
- data/lib/brakeman/report/report_text.rb +1 -1
- data/lib/brakeman/rescanner.rb +7 -5
- data/lib/brakeman/scanner.rb +47 -18
- data/lib/brakeman/tracker.rb +39 -4
- data/lib/brakeman/tracker/collection.rb +27 -5
- data/lib/brakeman/tracker/config.rb +73 -0
- data/lib/brakeman/tracker/controller.rb +1 -1
- data/lib/brakeman/tracker/method_info.rb +29 -0
- data/lib/brakeman/util.rb +17 -4
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +10 -2
- data/lib/brakeman/warning_codes.rb +2 -0
- data/lib/ruby_parser/bm_sexp.rb +9 -9
- metadata +149 -84
- data/bundle/ruby/2.7.0/gems/haml-5.1.2/lib/haml/escapable.rb +0 -50
- data/bundle/ruby/2.7.0/gems/ruby_parser-3.15.0/debugging.md +0 -57
- 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='<'/>
|
2175
|
+
# </root>
|
2176
|
+
# EOT
|
2177
|
+
# d = REXML::Document.new(xml_string)
|
2178
|
+
# ele = d.elements['//ele'] # => <a foo:att='1' bar:att='2' att='<'/>
|
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='<'/>
|
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='<'/>
|
2204
|
+
# attrs = ele.attributes.to_a # => [foo:att='1', bar:att='2', att='<']
|
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='<'/>
|
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='<'/>
|
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='<'/>
|
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='<'/>
|
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='<']
|
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='<'/>
|
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='<'/>
|
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='<'/>
|
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='<'/>
|
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='<'
|
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='<'/>
|
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='<'/>
|
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='<'/>
|
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='<'/>
|
2479
|
+
# attrs = ele.attributes
|
2480
|
+
# attrs.delete('foo:att') # => <ele bar:att='2' att='<'/>
|
2481
|
+
# attrs.delete('foo:att') # => <ele bar:att='2' att='<'/>
|
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='<'/> # => <ele att='<'/>
|
2488
|
+
# attrs.delete(attr) # => <ele att='<'/> # => <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='<'/>
|
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='<'/>
|
2531
|
+
# attrs = ele.attributes
|
2532
|
+
# attrs # => {"att"=>{"foo"=>foo:att='1', "bar"=>bar:att='2', ""=>att='<'}}
|
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='<'/>
|
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='<'/>
|
2556
|
+
# attrs = ele.attributes
|
2557
|
+
# attrs.delete_all('att') # => [att='<']
|
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='<'/>
|
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='<'/>
|
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
|