1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
|
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = XMLElement.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
# by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
require 'taskjuggler/UTF8String'
class TaskJuggler
# This class models an XML node that may contain other XML nodes. XML element
# trees can be constructed with the class constructor and converted into XML.
class XMLElement
# Construct a new XML element and include it in an existing XMLElement tree.
def initialize(name, attributes = {}, selfClosing = false, &block)
if (name.nil? && attributes.length > 0) ||
(!name.nil? && !name.is_a?(String))
raise ArgumentError, "Name must be nil or a String "
end
@name = name
attributes.each do |n, v|
if n.nil? || v.nil?
raise ArgumentError,
"Attribute name (#{n}) or value (#{v}) may not be nil"
end
unless v.is_a?(String)
raise ArgumentError,
"Attribute value of #{n} must be a String"
end
end
@attributes = attributes
# This can be set to true if <name /> is legal for this element.
@selfClosing = selfClosing
@children = block ? yield(block) : []
# Allow blocks with single elements not to be Arrays. They will be
# automatically converted into Arrays here.
unless @children.is_a?(Array)
@children = [ @children ]
else
@children.flatten!
end
# Convert all children that are text String objects into XMLText
# objects.
@children.collect! do |c|
c.is_a?(String) ? XMLText.new(c) : c
end
# Make sure we have no nil objects in the list.
@children.delete_if { |c| c.nil? }
# Now all children must be XMLElement objects.
@children.each do |c|
unless c.is_a?(XMLElement)
raise ArgumentError,
"Element must be of type XMLElement, not #{c.class}: #{c.inspect}"
end
end
end
# Add a new child or a set of new childs to the element.
def <<(arg)
# If the argument is an array, we have to insert each element
# individually.
if arg.is_a?(XMLElement)
@children << arg
elsif arg.is_a?(String)
@children << XMLText.new(arg)
elsif arg.is_a?(Array)
# Delete all nil entries
arg.delete_if { |i| i.nil? }
# Check that the rest are really all XMLElement objects.
arg.each do |i|
unless i.is_a?(XMLElement)
raise ArgumentError,
"Element must be of type XMLElement, not #{i.class}: #{i.inspect}"
end
end
@children += arg
elsif arg.nil?
# Do nothing. Insertions of nil are simply ignored.
else
raise "Elements must be of type XMLElement not #{arg.class}"
end
self
end
# Add or change _attribute_ to _value_.
def []=(attribute, value)
raise ArgumentError,
"Attribute value #{value} is not a String" unless value.is_a?(String)
@attributes[attribute] = value
end
# Return the value of attribute _attribute_.
def [](attribute)
@attributes[attribute]
end
# Return the element and all sub elements as properly formatted XML.
def to_s(indent = 0)
out = '<' + @name
@attributes.keys.sort.each do |attrName|
out << " #{attrName}=\"#{escape(@attributes[attrName], true)}\""
end
if @children.empty? && @selfClosing
out << '/>'
else
out << '>'
@children.each do |child|
# We only insert newlines for multiple childs and after a tag has been
# closed.
if @children.size > 1 && !child.is_a?(XMLText) && out[-1] == ?>
out << "\n" + indentation(indent + 1)
end
out << child.to_s(indent + 1)
end
out << "\n" + indentation(indent) if @children.size > 1 && out[-1] == ?>
out << '</' + @name + '>'
end
end
protected
# Escape special characters in input String _str_.
def escape(str, quotes = false)
out = ''
str.each_utf8_char do |c|
case c
when '&'
out << '&'
when '"'
out << '\"'
else
out << c
end
end
out
end
def indentation(indent)
' ' * indent
end
end
# This is a specialized XMLElement to represent a simple text.
class XMLText < XMLElement
def initialize(text)
super(nil, {})
raise 'Text may not be nil' unless text
@text = text
end
def to_s(indent)
out = ''
@text.each_utf8_char do |c|
case c
when '<'
out << '<'
when '>'
out << '>'
when '&'
out << '&'
else
out << c
end
end
out
end
end
# This is a convenience class that allows the creation of an XMLText nested
# into an XMLElement. The _name_ and _attributes_ belong to the XMLElement,
# the text to the XMLText.
class XMLNamedText < XMLElement
def initialize(text, name, attributes = {})
super(name, attributes)
self << XMLText.new(text)
end
end
# This is a specialized XMLElement to represent a comment.
class XMLComment < XMLElement
def initialize(text = '')
super(nil, {})
@text = text
end
def to_s(indent)
'<!-- ' + canonicalize_comment(@text) + " -->\n#{' ' * indent}"
end
private
# It is crucial to canonicalize xml comment text because xml
# comment syntax forbids having a -- in the comment body. I
# picked emacs's "M-x comment-region" approach of putting a
# backslash between the two.
def canonicalize_comment(text)
new_text = text.gsub("--", "-\\-")
new_text
end
end
# This is a specialized XMLElement to represent XML blobs. The content is not
# interpreted and must be valid XML in the content it is added.
class XMLBlob < XMLElement
def initialize(blob = '')
super(nil, {})
raise ArgumentError, "blob may not be nil" if blob.nil?
@blob = blob
end
def to_s(indent)
out = ''
@blob.each_utf8_char do |c|
out += (c == "\n" ? "\n" + ' ' * indent : c)
end
out
end
end
end
|