Welcome to the "trac"-ing site of soap4r!
[soap4r] [httpclient] [openpgp4u] [pkcs1] [logger] [csv] [vtr]

root/trunk/lib/xsd/datatypes.rb

Revision 1993, 27.5 kB (checked in by nahi, 1 year ago)
  • (merged from branches/1_5: -r1989:1992)
    • simpleRestriction + minExclusive/maxExclusive caused NameError?. closes #438.
    • parsing empty element for enumeration type caused NameError?. closes #440.
    • fixed wrongly implemented lexical representations for gMonth, gMonthDay and gDay. closes #439.
  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
<
Line 
1 # XSD4R - XML Schema Datatype implementation.
2 # Copyright (C) 2000-2007  NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
4 # This program is copyrighted free software by NAKAMURA, Hiroshi.  You can
5 # redistribute it and/or modify it under the same terms of Ruby's license;
6 # either the dual license version in 2003, or any later version.
7
8
9 require 'xsd/qname'
10 require 'xsd/charset'
11 require 'soap/nestedexception'
12 require 'uri'
13
14
15 ###
16 ## XMLSchamaDatatypes general definitions.
17 #
18 module XSD
19
20
21 Namespace = 'http://www.w3.org/2001/XMLSchema'
22 InstanceNamespace = 'http://www.w3.org/2001/XMLSchema-instance'
23
24 AttrType = 'type'
25 NilValue = 'true'
26
27 AnyTypeLiteral = 'anyType'
28 AnySimpleTypeLiteral = 'anySimpleType'
29 NilLiteral = 'nil'
30 StringLiteral = 'string'
31 BooleanLiteral = 'boolean'
32 DecimalLiteral = 'decimal'
33 FloatLiteral = 'float'
34 DoubleLiteral = 'double'
35 DurationLiteral = 'duration'
36 DateTimeLiteral = 'dateTime'
37 TimeLiteral = 'time'
38 DateLiteral = 'date'
39 GYearMonthLiteral = 'gYearMonth'
40 GYearLiteral = 'gYear'
41 GMonthDayLiteral = 'gMonthDay'
42 GDayLiteral = 'gDay'
43 GMonthLiteral = 'gMonth'
44 HexBinaryLiteral = 'hexBinary'
45 Base64BinaryLiteral = 'base64Binary'
46 AnyURILiteral = 'anyURI'
47 QNameLiteral = 'QName'
48
49 NormalizedStringLiteral = 'normalizedString'
50 TokenLiteral = 'token'
51 LanguageLiteral = 'language'
52 NMTOKENLiteral = 'NMTOKEN'
53 NMTOKENSLiteral = 'NMTOKENS'
54 NameLiteral = 'Name'
55 NCNameLiteral = 'NCName'
56 IDLiteral = 'ID'
57 IDREFLiteral = 'IDREF'
58 IDREFSLiteral = 'IDREFS'
59 ENTITYLiteral = 'ENTITY'
60 ENTITIESLiteral = 'ENTITIES'
61 IntegerLiteral = 'integer'
62 NonPositiveIntegerLiteral = 'nonPositiveInteger'
63 NegativeIntegerLiteral = 'negativeInteger'
64 LongLiteral = 'long'
65 IntLiteral = 'int'
66 ShortLiteral = 'short'
67 ByteLiteral = 'byte'
68 NonNegativeIntegerLiteral = 'nonNegativeInteger'
69 UnsignedLongLiteral = 'unsignedLong'
70 UnsignedIntLiteral = 'unsignedInt'
71 UnsignedShortLiteral = 'unsignedShort'
72 UnsignedByteLiteral = 'unsignedByte'
73 PositiveIntegerLiteral = 'positiveInteger'
74
75 AttrTypeName = QName.new(InstanceNamespace, AttrType)
76 AttrNilName = QName.new(InstanceNamespace, NilLiteral)
77
78 AnyTypeName = QName.new(Namespace, AnyTypeLiteral)
79 AnySimpleTypeName = QName.new(Namespace, AnySimpleTypeLiteral)
80
81 class Error < StandardError; include ::SOAP::NestedException; end
82 class ValueSpaceError < Error; end
83
84
85 ###
86 ## The base class of all datatypes with Namespace.
87 #
88 class NSDBase
89   @@types = []
90
91   attr_accessor :type
92
93   def self.inherited(klass)
94     @@types << klass
95   end
96
97   def self.types
98     @@types
99   end
100
101   def initialize
102   end
103
104   def init(type)
105     @type = type
106   end
107 end
108
109
110 ###
111 ## The base class of XSD datatypes.
112 #
113 class XSDAnySimpleType < NSDBase
114   include XSD
115   Type = QName.new(Namespace, AnySimpleTypeLiteral)
116
117   # @data represents canonical space (ex. Integer: 123).
118   attr_reader :data
119   # @is_nil represents this data is nil or not.
120   attr_accessor :is_nil
121
122   def initialize(value = nil)
123     init(Type, value)
124   end
125
126   # true or raise
127   def check_lexical_format(value)
128     screen_data(value)
129     true
130   end
131
132   # set accepts a string which follows lexical space (ex. String: "+123"), or
133   # an object which follows canonical space (ex. Integer: 123).
134   def set(value)
135     if value.nil?
136       @is_nil = true
137       @data = nil
138       _set(nil)
139     else
140       @is_nil = false
141       _set(screen_data(value))
142     end
143   end
144
145   # to_s creates a string which follows lexical space (ex. String: "123").
146   def to_s()
147     if @is_nil
148       ""
149     else
150       _to_s
151     end
152   end
153
154 private
155
156   def init(type, value)
157     super(type)
158     set(value)
159   end
160
161   # raises ValueSpaceError if check failed
162   def screen_data(value)
163     value
164   end
165
166   def _set(value)
167     @data = value
168   end
169
170   def _to_s
171     @data.to_s
172   end
173 end
174
175 class XSDNil < XSDAnySimpleType
176   Type = QName.new(Namespace, NilLiteral)
177   Value = 'true'
178
179   def initialize(value = nil)
180     init(Type, value)
181   end
182 end
183
184
185 ###
186 ## Primitive datatypes.
187 #
188 class XSDString < XSDAnySimpleType
189   Type = QName.new(Namespace, StringLiteral)
190
191   def initialize(value = nil)
192     init(Type, value)
193   end
194
195   @@strict_ces_validation = false
196
197   def self.strict_ces_validation=(strict_ces_validation)
198     @@strict_ces_validation = strict_ces_validation
199   end
200
201   def self.strict_ces_validation
202     @@strict_ces_validation
203   end
204
205 private
206
207   def screen_data(value)
208     if ::XSD::XSDString.strict_ces_validation
209       externalces = XSD::Charset.encoding
210       unless XSD::Charset.is_ces(value, externalces)
211         raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
212       end
213     end
214     value
215   end
216 end
217
218 class XSDBoolean < XSDAnySimpleType
219   Type = QName.new(Namespace, BooleanLiteral)
220
221   def initialize(value = nil)
222     init(Type, value)
223   end
224
225 private
226
227   def screen_data(value)
228     if value.is_a?(String)
229       str = value.strip
230       if str == 'true' || str == '1'
231         true
232       elsif str == 'false' || str == '0'
233         false
234       else
235         raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.")
236       end
237     else
238       value ? true : false
239     end
240   end
241 end
242
243 class XSDDecimal < XSDAnySimpleType
244   Type = QName.new(Namespace, DecimalLiteral)
245
246   def initialize(value = nil)
247     init(Type, value)
248   end
249
250   def nonzero?
251     (@number != '0')
252   end
253
254 private
255
256   def screen_data(d)
257     if d.is_a?(String)
258       # Integer("00012") => 10 in Ruby.
259       d.sub!(/^([+\-]?)0*(?=\d)/, "\\1")
260     end
261     screen_data_str(d)
262   end
263
264   def screen_data_str(str)
265     /^([+\-]?)(\d*)(?:\.(\d*)?)?$/ =~ str.to_s.strip
266     unless Regexp.last_match
267       raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.")
268     end
269     sign = $1 || '+'
270     int_part = $2
271     frac_part = $3
272     int_part = '0' if int_part.empty?
273     frac_part = frac_part ? frac_part.sub(/0+$/, '') : ''
274     point = - frac_part.size
275     number = int_part + frac_part
276     # normalize
277     if sign == '+'
278       sign = ''
279     elsif sign == '-'
280       if number == '0'
281         sign = ''
282       end
283     end
284     [sign, point, number]
285   end
286
287   def _set(data)
288     if data.nil?
289       @sign = @point = @number = @data = nil
290       return
291     end
292     @sign, @point, @number = data
293     @data = _to_s
294     @data.freeze
295   end
296
297   # 0.0 -> 0; right?
298   def _to_s
299     str = @number.dup
300     if @point.nonzero?
301       str[@number.size + @point, 0] = '.'
302     end
303     @sign + str
304   end
305 end
306
307 module FloatConstants
308   NaN = 0.0/0.0
309   POSITIVE_INF = +1.0/0.0
310   NEGATIVE_INF = -1.0/0.0
311   POSITIVE_ZERO = +1.0/POSITIVE_INF
312   NEGATIVE_ZERO = -1.0/POSITIVE_INF
313   MIN_POSITIVE_SINGLE = 2.0 ** -149
314 end
315
316 class XSDFloat < XSDAnySimpleType
317   include FloatConstants
318   Type = QName.new(Namespace, FloatLiteral)
319
320   def initialize(value = nil)
321     init(Type, value)
322   end
323
324 private
325
326   def screen_data(value)
327     # "NaN".to_f => 0 in some environment.  libc?
328     if value.is_a?(Float)
329       return narrow32bit(value)
330     end
331     str = value.to_s.strip
332     if str == 'NaN'
333       NaN
334     elsif str == 'INF'
335       POSITIVE_INF
336     elsif str == '-INF'
337       NEGATIVE_INF
338     else
339       if /^[+\-\.\deE]+$/ !~ str
340         raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.")
341       end
342       # Float("-1.4E") might fail on some system.
343       str << '0' if /e$/i =~ str
344       begin
345         return narrow32bit(Float(str))
346       rescue ArgumentError
347         raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.", $!)
348       end
349     end
350   end
351
352   def _to_s
353     if @data.nan?
354       'NaN'
355     elsif @data.infinite? == 1
356       'INF'
357     elsif @data.infinite? == -1
358       '-INF'
359     else
360       sign = XSDFloat.positive?(@data) ? '+' : '-'
361       sign + sprintf("%.10g", @data.abs).sub(/[eE]([+-])?0+/) { 'e' + $1 }
362     end
363   end
364
365   # Convert to single-precision 32-bit floating point value.
366   def narrow32bit(f)
367     if f.nan? || f.infinite?
368       f
369     elsif f.abs < MIN_POSITIVE_SINGLE
370       XSDFloat.positive?(f) ? POSITIVE_ZERO : NEGATIVE_ZERO
371     else
372       f
373     end
374   end
375
376   def self.positive?(value)
377     (1 / value) > 0.0
378   end
379 end
380
381 # Ruby's Float is double-precision 64-bit floating point value.
382 class XSDDouble < XSDAnySimpleType
383   include FloatConstants
384   Type = QName.new(Namespace, DoubleLiteral)
385
386   def initialize(value = nil)
387     init(Type, value)
388   end
389
390 private
391
392   def screen_data(value)
393     # "NaN".to_f => 0 in some environment.  libc?
394     if value.is_a?(Float)
395       return value
396     end
397     str = value.to_s.strip
398     if str == 'NaN'
399       NaN
400     elsif str == 'INF'
401       POSITIVE_INF
402     elsif str == '-INF'
403       NEGATIVE_INF
404     else
405       begin
406         return Float(str)
407       rescue ArgumentError
408         # '1.4e' cannot be parsed on some architecture.
409         if /e\z/i =~ str
410           begin
411             return Float(str + '0')
412           rescue ArgumentError
413             raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.", $!)
414           end
415         else
416           raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.", $!)
417         end
418       end
419     end
420   end
421
422   def _to_s
423     if @data.nan?
424       'NaN'
425     elsif @data.infinite? == 1
426       'INF'
427     elsif @data.infinite? == -1
428       '-INF'
429     else
430       sign = (1 / @data > 0.0) ? '+' : '-'
431       sign + sprintf("%.16g", @data.abs).sub(/[eE]([+-])?0+/) { 'e' + $1 }
432     end
433   end
434 end
435
436 class XSDDuration < XSDAnySimpleType
437   Type = QName.new(Namespace, DurationLiteral)
438
439   attr_accessor :sign
440   attr_accessor :year
441   attr_accessor :month
442   attr_accessor :day
443   attr_accessor :hour
444   attr_accessor :min
445   attr_accessor :sec
446
447   def initialize(value = nil)
448     init(Type, value)
449   end
450
451 private
452
453   def screen_data(value)
454     /^([+\-]?)P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/ =~ value.to_s.strip
455     unless Regexp.last_match
456       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
457     end
458     if $5 and !$6 and !$7 and !$8
459       # allows durations lower than a day such as 'PT5S'.
460       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
461     end
462     sign = $1
463     year = $2.to_i
464     month = $3.to_i
465     day = $4.to_i
466     hour = $6.to_i
467     min = $7.to_i
468     sec = $8 ? XSDDecimal.new($8) : 0
469     [sign, year, month, day, hour, min, sec]
470   end
471
472   def _set(data)
473     if data.nil?
474       @sign = @year = @month = @day = @hour = @min = @sec = @data = nil
475       return
476     end
477     @sign, @year, @month, @day, @hour, @min, @sec = data
478     @data = _to_s
479     @data.freeze
480   end
481
482   def _to_s
483     str = ''
484     str << @sign if @sign
485     str << 'P'
486     l = ''
487     l << "#{ @year }Y" if @year.nonzero?
488     l << "#{ @month }M" if @month.nonzero?
489     l << "#{ @day }D" if @day.nonzero?
490     r = ''
491     r << "#{ @hour }H" if @hour.nonzero?
492     r << "#{ @min }M" if @min.nonzero?
493     r << "#{ @sec }S" if @sec.nonzero?
494     str << l
495     unless r.empty?
496       str << "T" << r
497     end
498     if l.empty? and r.empty?
499       str << "0D"
500     end
501     str
502   end
503 end
504
505
506 require 'rational'
507 require 'date'
508
509 module XSDDateTimeImpl
510   DayInSec = 86400      # 24 * 60 * 60
511   DayInMicro = 86400_000_000
512
513   def to_obj(klass)
514     if klass == Time
515       to_time
516     elsif klass == Date
517       to_date
518     elsif klass == DateTime
519       to_datetime
520     else
521       nil
522     end
523   end
524
525   def to_time
526     begin
527       if @data.offset * DayInSec == Time.now.utc_offset
528         d = @data
529         usec = (d.sec_fraction * DayInMicro).round
530         Time.local(d.year, d.month, d.mday, d.hour, d.min, d.sec, usec)
531       else
532         d = @data.newof
533         usec = (d.sec_fraction * DayInMicro).round
534         Time.gm(d.year, d.month, d.mday, d.hour, d.min, d.sec, usec)
535       end
536     rescue ArgumentError
537       nil
538     end
539   end
540
541   def to_date
542     Date.new0(@data.class.jd_to_ajd(@data.jd, 0, 0), 0, @data.start)
543   end
544
545   def to_datetime
546     data
547   end
548
549   def tz2of(str)
550     /^(?:Z|(?:([+\-])(\d\d):(\d\d))?)$/ =~ str
551     sign = $1
552     hour = $2.to_i
553     min = $3.to_i
554
555     of = case sign
556       when '+'
557         of = +(hour.to_r * 60 + min) / 1440     # 24 * 60
558       when '-'
559         of = -(hour.to_r * 60 + min) / 1440     # 24 * 60
560       else
561         0
562       end
563     of
564   end
565
566   def of2tz(offset)
567     diffmin = offset * 24 * 60
568     if diffmin.zero?
569       'Z'
570     else
571       ((diffmin < 0) ? '-' : '+') << format('%02d:%02d',
572         (diffmin.abs / 60.0).to_i, (diffmin.abs % 60.0).to_i)
573     end
574   end
575
576   def screen_data(t)
577     # convert t to a DateTime as an internal representation.
578     if t.respond_to?(:to_datetime)      # 1.9 or later
579       t.to_datetime
580     elsif t.is_a?(DateTime)
581       t
582     elsif t.is_a?(Date)
583       t = screen_data_str(t)
584       t <<= 12 if t.year < 0
585       t
586     elsif t.is_a?(Time)
587       jd = DateTime.civil_to_jd(t.year, t.mon, t.mday, DateTime::ITALY)
588       fr = DateTime.time_to_day_fraction(t.hour, t.min, [t.sec, 59].min) +
589         t.usec.to_r / DayInMicro
590       of = t.utc_offset.to_r / DayInSec
591       DateTime.new0(DateTime.jd_to_ajd(jd, fr, of), of, DateTime::ITALY)
592     else
593       screen_data_str(t)
594     end
595   end
596
597   def add_tz(s)
598     s + of2tz(@data.offset)
599   end
600 end
601
602 class XSDDateTime < XSDAnySimpleType
603   include XSDDateTimeImpl
604   Type = QName.new(Namespace, DateTimeLiteral)
605
606   def initialize(value = nil)
607     init(Type, value)
608   end
609
610 private
611
612   def screen_data_str(t)
613     /^([+\-]?\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d(?:\.(\d*))?)(Z|(?:[+\-]\d\d:\d\d)?)?$/ =~ t.to_s.strip
614     unless Regexp.last_match
615       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
616     end
617     if $1 == '0000'
618       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
619     end
620     year = $1.to_i
621     if year < 0
622       year += 1
623     end
624     mon = $2.to_i
625     mday = $3.to_i
626     hour = $4.to_i
627     min = $5.to_i
628     sec = $6.to_i
629     secfrac = $7
630     zonestr = $8
631     data = DateTime.civil(year, mon, mday, hour, min, sec, tz2of(zonestr))
632     if secfrac
633       diffday = secfrac.to_i.to_r / (10 ** secfrac.size) / DayInSec
634       data += diffday
635       # FYI: new0 and jd_to_rjd are not necessary to use if you don't have
636       # exceptional reason.
637     end
638     [data, secfrac]
639   end
640
641   def _set(data)
642     if data.nil?
643       @data = @secfrac = nil
644       return
645     end
646     @data, @secfrac = data
647   end
648
649   def _to_s
650     year = (@data.year > 0) ? @data.year : @data.year - 1
651     s = format('%.4d-%02d-%02dT%02d:%02d:%02d',
652       year, @data.mon, @data.mday, @data.hour, @data.min, @data.sec)
653     if @data.sec_fraction.nonzero?
654       if @secfrac
655         s << ".#{ @secfrac }"
656       else
657         s << sprintf("%.16f",
658           (@data.sec_fraction * DayInSec).to_f).sub(/^0/, '').sub(/0*$/, '')
659       end
660     end
661     add_tz(s)
662   end
663 end
664
665 class XSDTime < XSDAnySimpleType
666   include XSDDateTimeImpl
667   Type = QName.new(Namespace, TimeLiteral)
668
669   def initialize(value = nil)
670     init(Type, value)
671   end
672
673 private
674
675   def screen_data_str(t)
676     /^(\d\d):(\d\d):(\d\d(?:\.(\d*))?)(Z|(?:([+\-])(\d\d):(\d\d))?)?$/ =~ t.to_s.strip
677     unless Regexp.last_match
678       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
679     end
680     hour = $1.to_i
681     min = $2.to_i
682     sec = $3.to_i
683     secfrac = $4
684     zonestr = $5
685     data = DateTime.civil(1, 1, 1, hour, min, sec, tz2of(zonestr))
686     if secfrac
687       diffday = secfrac.to_i.to_r / (10 ** secfrac.size) / DayInSec
688       data += diffday
689     end
690     [data, secfrac]
691   end
692
693   def _set(data)
694     if data.nil?
695       @data = @secfrac = nil
696       return
697     end
698     @data, @secfrac = data
699   end
700
701   def _to_s
702     s = format('%02d:%02d:%02d', @data.hour, @data.min, @data.sec)
703     if @data.sec_fraction.nonzero?
704       if @secfrac
705         s << ".#{ @secfrac }"
706       else
707         s << sprintf("%.16f",
708           (@data.sec_fraction * DayInSec).to_f).sub(/^0/, '').sub(/0*$/, '')
709       end
710     end
711     add_tz(s)
712   end
713 end
714
715 class XSDDate < XSDAnySimpleType
716   include XSDDateTimeImpl
717   Type = QName.new(Namespace, DateLiteral)
718
719   def initialize(value = nil)
720     init(Type, value)
721   end
722
723 private
724
725   def screen_data_str(t)
726     /^([+\-]?\d{4,})-(\d\d)-(\d\d)(Z|(?:([+\-])(\d\d):(\d\d))?)?$/ =~ t.to_s.strip
727     unless Regexp.last_match
728       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
729     end
730     year = $1.to_i
731     if year < 0
732       year += 1
733     end
734     mon = $2.to_i
735     mday = $3.to_i
736     zonestr = $4
737     DateTime.civil(year, mon, mday, 0, 0, 0, tz2of(zonestr))
738   end
739
740   def _to_s
741     year = (@data.year > 0) ? @data.year : @data.year - 1
742     s = format('%.4d-%02d-%02d', year, @data.mon, @data.mday)
743     add_tz(s)
744   end
745 end
746
747 class XSDGYearMonth < XSDAnySimpleType
748   include XSDDateTimeImpl
749   Type = QName.new(Namespace, GYearMonthLiteral)
750
751   def initialize(value = nil)
752     init(Type, value)
753   end
754
755 private
756
757   def screen_data_str(t)
758     /^([+\-]?\d{4,})-(\d\d)(Z|(?:([+\-])(\d\d):(\d\d))?)?$/ =~ t.to_s.strip
759     unless Regexp.last_match
760       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
761     end
762     year = $1.to_i
763     if year < 0
764       year += 1
765     end
766     mon = $2.to_i
767     zonestr = $3
768     DateTime.civil(year, mon, 1, 0, 0, 0, tz2of(zonestr))
769   end
770
771   def _to_s
772     year = (@data.year > 0) ? @data.year : @data.year - 1
773     s = format('%.4d-%02d', year, @data.mon)
774     add_tz(s)
775   end
776 end
777
778 class XSDGYear < XSDAnySimpleType
779   include XSDDateTimeImpl
780   Type = QName.new(Namespace, GYearLiteral)
781
782   def initialize(value = nil)
783     init(Type, value)
784   end
785
786 private
787
788   def screen_data_str(t)
789     /^([+\-]?\d{4,})(Z|(?:([+\-])(\d\d):(\d\d))?)?$/ =~ t.to_s.strip
790     unless Regexp.last_match
791       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
792     end
793     year = $1.to_i
794     if year < 0
795       year += 1
796     end
797     zonestr = $2
798     DateTime.civil(year, 1, 1, 0, 0, 0, tz2of(zonestr))
799   end
800
801   def _to_s
802     year = (@data.year > 0) ? @data.year : @data.year - 1
803     s = format('%.4d', year)
804     add_tz(s)
805   end
806 end
807
808 class XSDGMonthDay < XSDAnySimpleType
809   include XSDDateTimeImpl
810   Type = QName.new(Namespace, GMonthDayLiteral)
811
812   def initialize(value = nil)
813     init(Type, value)
814   end
815
816 private
817
818   def screen_data_str(t)
819     /^--(\d\d)-(\d\d)(Z|(?:[+\-]\d\d:\d\d)?)?$/ =~ t.to_s.strip
820     unless Regexp.last_match
821       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
822     end
823     mon = $1.to_i
824     mday = $2.to_i
825     zonestr = $3
826     DateTime.civil(1, mon, mday, 0, 0, 0, tz2of(zonestr))
827   end
828
829   def _to_s
830     s = format('--%02d-%02d', @data.mon, @data.mday)
831     add_tz(s)
832   end
833 end
834
835 class XSDGDay < XSDAnySimpleType
836   include XSDDateTimeImpl
837   Type = QName.new(Namespace, GDayLiteral)
838
839   def initialize(value = nil)
840     init(Type, value)
841   end
842
843 private
844
845   def screen_data_str(t)
846     /^---(\d\d)(Z|(?:[+\-]\d\d:\d\d)?)?$/ =~ t.to_s.strip
847     unless Regexp.last_match
848       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
849     end
850     mday = $1.to_i
851     zonestr = $2
852     DateTime.civil(1, 1, mday, 0, 0, 0, tz2of(zonestr))
853   end
854
855   def _to_s
856     s = format('---%02d', @data.mday)
857     add_tz(s)
858   end
859 end
860
861 class XSDGMonth < XSDAnySimpleType
862   include XSDDateTimeImpl
863   Type = QName.new(Namespace, GMonthLiteral)
864
865   def initialize(value = nil)
866     init(Type, value)
867   end
868
869 private
870
871   def screen_data_str(t)
872     /^--(\d\d)(Z|(?:[+\-]\d\d:\d\d)?)?$/ =~ t.to_s.strip
873     unless Regexp.last_match
874       raise ValueSpaceError.new("#{ type }: cannot accept '#{ t }'.")
875     end
876     mon = $1.to_i
877     zonestr = $2
878     DateTime.civil(1, mon, 1, 0, 0, 0, tz2of(zonestr))
879   end
880
881   def _to_s
882     s = format('--%02d', @data.mon)
883     add_tz(s)
884   end
885 end
886
887 class XSDHexBinary < XSDAnySimpleType
888   Type = QName.new(Namespace, HexBinaryLiteral)
889
890   # String in Ruby could be a binary.
891   def initialize(value = nil)
892     init(Type, value)
893   end
894
895   def set_encoded(value)
896     if /^[0-9a-fA-F]*$/ !~ value
897       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
898     end
899     @data = String.new(value).strip
900     @is_nil = false
901   end
902
903   def string
904     [@data].pack("H*")
905   end
906
907 private
908
909   def screen_data(value)
910     value.unpack("H*")[0].tr('a-f', 'A-F')
911   end
912 end
913
914 class XSDBase64Binary < XSDAnySimpleType
915   Type = QName.new(Namespace, Base64BinaryLiteral)
916
917   # String in Ruby could be a binary.
918   def initialize(value = nil)
919     init(Type, value)
920   end
921
922   def set_encoded(value)
923     if /^[A-Za-z0-9+\/=]*$/ !~ value
924       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
925     end
926     @data = String.new(value).strip
927     @is_nil = false
928   end
929
930   def string
931     @data.unpack("m")[0]
932   end
933
934 private
935
936   def screen_data(value)
937     [value].pack("m").strip
938   end
939 end
940
941 class XSDAnyURI < XSDAnySimpleType
942   Type = QName.new(Namespace, AnyURILiteral)
943
944   def initialize(value = nil)
945     init(Type, value)
946   end
947
948 private
949
950   def screen_data(value)
951     begin
952       URI.parse(value.to_s.strip)
953     rescue URI::InvalidURIError
954       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.", $!)
955     end
956   end
957 end
958
959 class XSDQName < XSDAnySimpleType
960   Type = QName.new(Namespace, QNameLiteral)
961
962   def initialize(value = nil)
963     init(Type, value)
964   end
965
966 private
967
968   def screen_data(value)
969     /^(?:([^:]+):)?([^:]+)$/ =~ value.to_s.strip
970     unless Regexp.last_match
971       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
972     end
973     prefix = $1
974     localpart = $2
975     [prefix, localpart]
976   end
977
978   def _set(data)
979     if data.nil?
980       @prefix = @localpart = @data = nil
981       return
982     end
983     @prefix, @localpart = data
984     @data = _to_s
985     @data.freeze
986   end
987
988   def _to_s
989     if @prefix
990       "#{ @prefix }:#{ @localpart }"
991     else
992       "#{ @localpart }"
993     end
994   end
995 end
996
997
998 ###
999 ## Derived types
1000 #
1001 class XSDNormalizedString < XSDString
1002   Type = QName.new(Namespace, NormalizedStringLiteral)
1003
1004   def initialize(value = nil)
1005     init(Type, value)
1006   end
1007
1008 private
1009
1010   def screen_data(value)
1011     super
1012     if /[\t\r\n]/ =~ value
1013       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
1014     end
1015     value
1016   end
1017 end
1018
1019 class XSDToken < XSDNormalizedString
1020   Type = QName.new(Namespace, TokenLiteral)
1021
1022   def initialize(value = nil)
1023     init(Type, value)
1024   end
1025
1026 private
1027
1028   def screen_data(value)
1029     super
1030     if /\A / =~ value or / \Z/ =~ value or value.index('  ')
1031       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
1032     end
1033     value
1034   end
1035 end
1036
1037 class XSDLanguage < XSDToken
1038   Type = QName.new(Namespace, LanguageLiteral)
1039
1040   def initialize(value = nil)
1041     init(Type, value)
1042   end
1043
1044 private
1045
1046   def screen_data(value)
1047     super
1048     # RFC 3066 syntax check
1049     if /\A[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})?\z/ !~ value
1050       raise ValueSpaceError.new("#{ type }: cannot accept '#{ value }'.")
1051     end
1052     value
1053   end
1054 end
1055
1056 class XSDNMTOKEN < XSDToken
1057   Type = QName.new(Namespace, NMTOKENLiteral)
1058
1059   def initialize(value = nil)
1060     init(Type, value)
1061   end
1062
1063 private
1064
1065   def screen_data(value)
1066     super
1067     # TODO: check lexical space and convert to a value
1068     value
1069   end
1070 end
1071
1072 class XSDNMTOKENS < XSDNMTOKEN
1073   Type = QName.new(Namespace, NMTOKENSLiteral)
1074
1075   def initialize(value = nil)
1076     init(Type, value)
1077   end
1078
1079 private
1080
1081   def screen_data(value)
1082     # derived by list
1083     # TODO: check lexical space and convert to a value
1084     value
1085   end
1086 end
1087
1088 class XSDName < XSDToken
1089   Type = QName.new(Namespace, NameLiteral)
1090
1091   def initialize(value = nil)
1092     init(Type, value)
1093   end
1094
1095 private
1096
1097   def screen_data(value)
1098     super
1099     # TODO: check lexical space and convert to a value
1100     value
1101   end
1102 end
1103
1104 class XSDNCName < XSDName
1105   Type = QName.new(Namespace, NCNameLiteral)
1106
1107   def initialize(value = nil)
1108     init(Type, value)
1109   end
1110
1111 private
1112
1113   def screen_data(value)
1114     super
1115     # TODO: check lexical space and convert to a value
1116     value
1117   end
1118 end
1119
1120 class XSDID < XSDNCName
1121   Type = QName.new(Namespace, IDLiteral)
1122
1123   def initialize(value = nil)
1124     init(Type, value)
1125   end
1126
1127 private
1128
1129   def screen_data(value)
1130     super
1131     # TODO: check lexical space and convert to a value
1132     value
1133   end
1134 end
1135
1136 class XSDIDREF < XSDNCName
1137   Type = QName.new(Namespace, IDREFLiteral)
1138
1139   def initialize(value = nil)
1140     init(Type, value)
1141   end
1142
1143 private
1144
1145   def screen_data(value)
1146     super
1147     # TODO: check lexical space and convert to a value
1148     value
1149   end
1150 end
1151
1152 class XSDIDREFS < XSDIDREF
1153   Type = QName.new(Namespace, IDREFSLiteral)
1154
1155   def initialize(value = nil)
1156     init(Type, value)
1157   end
1158
1159 private
1160
1161   def screen_data(value)
1162     # derived by list
1163     # TODO: check lexical space and convert to a value
1164     value
1165   end
1166 end
1167
1168 class XSDENTITY < XSDNCName
1169   Type = QName.new(Namespace, ENTITYLiteral)
1170
1171   def initialize(value = nil)
1172     init(Type, value)
1173   end
1174
1175 private
1176
1177   def screen_data(value)
1178     super
1179     # TODO: check lexical space and convert to a value
1180     value
1181   end
1182 end
1183
1184 class XSDENTITIES < XSDENTITY
1185   Type = QName.new(Namespace, ENTITIESLiteral)
1186
1187   def initialize(value = nil)
1188     init(Type, value)
1189   end
1190
1191 private
1192
1193   def screen_data(value)
1194     # derived by list
1195     # TODO: check lexical space and convert to a value
1196     value
1197   end
1198 end
1199
1200 class XSDInteger < XSDDecimal
1201   Type = QName.new(Namespace, IntegerLiteral)
1202
1203   def initialize(value = nil)
1204     init(Type, value)
1205   end
1206
1207 private
1208
1209   def screen_data_str(str)
1210     begin
1211       data = Integer(str)
1212     rescue ArgumentError
1213       raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.", $!)
1214     end
1215     unless validate(data)
1216       raise ValueSpaceError.new("#{ type }: cannot accept '#{ str }'.")
1217     end
1218     data
1219   end
1220
1221   def _set(value)
1222     @data = value
1223   end
1224
1225   def _to_s()
1226     @data.to_s
1227   end
1228
1229   def validate(v)
1230     max = maxinclusive
1231     min = mininclusive
1232     (max.nil? or v <= max) and (min.nil? or v >= min)
1233   end
1234
1235   def maxinclusive
1236     nil
1237   end
1238
1239   def mininclusive
1240     nil
1241   end
1242
1243   PositiveMinInclusive = 1
1244   def positive(v)
1245     PositiveMinInclusive <= v
1246   end
1247 end
1248
1249 class XSDNonPositiveInteger < XSDInteger
1250   Type = QName.new(Namespace, NonPositiveIntegerLiteral)
1251
1252   def initialize(value = nil)
1253     init(Type, value)
1254   end
1255
1256 private
1257
1258   def maxinclusive
1259     0
1260   end
1261
1262   def mininclusive
1263     nil
1264   end
1265 end
1266
1267 class XSDNegativeInteger < XSDNonPositiveInteger
1268   Type = QName.new(Namespace, NegativeIntegerLiteral)
1269
1270   def initialize(value = nil)
1271     init(Type, value)
1272   end
1273
1274 private
1275
1276   def maxinclusive
1277     -1
1278   end
1279
1280   def mininclusive
1281     nil
1282   end
1283 end
1284
1285 class XSDLong < XSDInteger
1286   Type = QName.new(Namespace, LongLiteral)
1287
1288   def initialize(value = nil)
1289     init(Type, value)
1290   end
1291
1292 private
1293
1294   def maxinclusive
1295     +9223372036854775807
1296   end
1297
1298   def mininclusive
1299     -9223372036854775808
1300   end
1301 end
1302
1303 class XSDInt < XSDLong
1304   Type = QName.new(Namespace, IntLiteral)
1305
1306   def initialize(value = nil)
1307     init(Type, value)
1308   end
1309
1310 private
1311
1312   def maxinclusive
1313     +2147483647
1314   end
1315
1316   def mininclusive
1317     -2147483648
1318   end
1319 end
1320
1321 class XSDShort < XSDInt
1322   Type = QName.new(Namespace, ShortLiteral)
1323
1324   def initialize(value = nil)
1325     init(Type, value)
1326   end
1327
1328 private
1329
1330   def maxinclusive
1331     +32767
1332   end
1333
1334   def mininclusive
1335     -32768
1336   end
1337 end
1338
1339 class XSDByte < XSDShort
1340   Type = QName.new(Namespace, ByteLiteral)
1341
1342   def initialize(value = nil)
1343     init(Type, value)
1344   end
1345
1346 private
1347
1348   def maxinclusive
1349     +127
1350   end
1351
1352   def mininclusive
1353     -128
1354   end
1355 end
1356
1357 class XSDNonNegativeInteger < XSDInteger
1358   Type = QName.new(Namespace, NonNegativeIntegerLiteral)
1359
1360   def initialize(value = nil)
1361     init(Type, value)
1362   end
1363
1364 private
1365
1366   def maxinclusive
1367     nil
1368   end
1369
1370   def mininclusive
1371     0
1372   end
1373 end
1374
1375 class XSDUnsignedLong < XSDNonNegativeInteger
1376   Type = QName.new(Namespace, UnsignedLongLiteral)
1377
1378   def initialize(value = nil)
1379     init(Type, value)
1380   end
1381
1382 private
1383
1384   def maxinclusive
1385     +18446744073709551615
1386   end
1387
1388   def mininclusive
1389     0
1390   end
1391 end
1392
1393 class XSDUnsignedInt < XSDUnsignedLong
1394   Type = QName.new(Namespace, UnsignedIntLiteral)
1395
1396   def initialize(value = nil)
1397     init(Type, value)
1398   end
1399
1400 private
1401
1402   def maxinclusive
1403     +4294967295
1404   end
1405
1406   def mininclusive
1407     0
1408   end