facturx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE.txt +22 -0
  4. data/NOTICE.md +9 -0
  5. data/README.md +75 -0
  6. data/lib/facturx/attach.rb +34 -0
  7. data/lib/facturx/composers/ghostscript/defaults.rb +29 -0
  8. data/lib/facturx/composers/ghostscript/locator.rb +134 -0
  9. data/lib/facturx/composers/ghostscript/output_capture.rb +39 -0
  10. data/lib/facturx/composers/ghostscript/runner.rb +122 -0
  11. data/lib/facturx/composers/ghostscript/version_probe.rb +61 -0
  12. data/lib/facturx/composers/ghostscript.rb +105 -0
  13. data/lib/facturx/embedding.rb +15 -0
  14. data/lib/facturx/error.rb +22 -0
  15. data/lib/facturx/pdf/document.rb +103 -0
  16. data/lib/facturx/pdf/extractor.rb +115 -0
  17. data/lib/facturx/pdf/inspector.rb +17 -0
  18. data/lib/facturx/pdf/name_tree.rb +60 -0
  19. data/lib/facturx/pdf/verifier.rb +116 -0
  20. data/lib/facturx/profile.rb +5 -0
  21. data/lib/facturx/profiles.rb +60 -0
  22. data/lib/facturx/schema/1.09.2/basic/Factur-X_1.09.2_BASIC.xsd +20 -0
  23. data/lib/facturx/schema/1.09.2/basic/Factur-X_1.09.2_BASIC_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +86 -0
  24. data/lib/facturx/schema/1.09.2/basic/Factur-X_1.09.2_BASIC_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +60 -0
  25. data/lib/facturx/schema/1.09.2/basic/ReusableAggregateBusinessInformationEntity_100.xsd +249 -0
  26. data/lib/facturx/schema/1.09.2/basic-wl/Factur-X_1.09.2_BASICWL.xsd +20 -0
  27. data/lib/facturx/schema/1.09.2/basic-wl/Factur-X_1.09.2_BASICWL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +86 -0
  28. data/lib/facturx/schema/1.09.2/basic-wl/Factur-X_1.09.2_BASICWL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +53 -0
  29. data/lib/facturx/schema/1.09.2/basic-wl/ReusableAggregateBusinessInformationEntity_100.xsd +196 -0
  30. data/lib/facturx/schema/1.09.2/en16931/Factur-X_1.09.2_EN16931.xsd +20 -0
  31. data/lib/facturx/schema/1.09.2/en16931/Factur-X_1.09.2_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +94 -0
  32. data/lib/facturx/schema/1.09.2/en16931/Factur-X_1.09.2_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +84 -0
  33. data/lib/facturx/schema/1.09.2/en16931/ReusableAggregateBusinessInformationEntity_100.xsd +318 -0
  34. data/lib/facturx/schema/1.09.2/extended/Factur-X_1.09.2_EXTENDED.xsd +20 -0
  35. data/lib/facturx/schema/1.09.2/extended/Factur-X_1.09.2_EXTENDED_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +144 -0
  36. data/lib/facturx/schema/1.09.2/extended/Factur-X_1.09.2_EXTENDED_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +101 -0
  37. data/lib/facturx/schema/1.09.2/extended/ReusableAggregateBusinessInformationEntity_100.xsd +490 -0
  38. data/lib/facturx/schema/1.09.2/minimum/Factur-X_1.09.2_MINIMUM.xsd +20 -0
  39. data/lib/facturx/schema/1.09.2/minimum/Factur-X_1.09.2_MINIMUM_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +30 -0
  40. data/lib/facturx/schema/1.09.2/minimum/Factur-X_1.09.2_MINIMUM_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +38 -0
  41. data/lib/facturx/schema/1.09.2/minimum/ReusableAggregateBusinessInformationEntity_100.xsd +86 -0
  42. data/lib/facturx/schema/LICENSE-APACHE-2.0.txt +201 -0
  43. data/lib/facturx/schema/NOTICE.md +40 -0
  44. data/lib/facturx/schema/SHA256SUMS +20 -0
  45. data/lib/facturx/version.rb +5 -0
  46. data/lib/facturx/xml/parser.rb +23 -0
  47. data/lib/facturx/xml/profile_detector.rb +26 -0
  48. data/lib/facturx/xml/schema_validator.rb +64 -0
  49. data/lib/facturx/xml/verifier.rb +24 -0
  50. data/lib/facturx.rb +34 -0
  51. metadata +130 -0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Error < StandardError
5
+ attr_reader :details
6
+
7
+ def initialize(message = nil, **details)
8
+ @details = details.freeze
9
+ super(message)
10
+ end
11
+ end
12
+
13
+ class InvalidXmlError < Error; end
14
+ class UnknownProfileError < InvalidXmlError; end
15
+ class XsdValidationError < InvalidXmlError; end
16
+ class InvalidPdfError < Error; end
17
+ class ProtectedPdfError < InvalidPdfError; end
18
+ class ComposerUnavailableError < Error; end
19
+ class CompositionError < Error; end
20
+ class ExtractionError < Error; end
21
+ class VerificationError < Error; end
22
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pdf/reader'
4
+ require 'stringio'
5
+
6
+ require_relative '../error'
7
+
8
+ module Facturx
9
+ module Pdf
10
+ class Document
11
+ attr_reader :catalog, :reader
12
+
13
+ class << self
14
+ def open(pdf, reject_signed: false)
15
+ parse(pdf, reject_signed:)
16
+ rescue InvalidPdfError
17
+ raise
18
+ rescue PDF::Reader::MalformedPDFError, PDF::Reader::UnsupportedFeatureError,
19
+ ArgumentError, IOError => e
20
+ raise invalid_error(e)
21
+ end
22
+
23
+ private
24
+
25
+ def parse(pdf, reject_signed:)
26
+ raise InvalidPdfError.new('PDF must be a byte string', reason: :invalid_input) unless pdf.is_a?(String)
27
+
28
+ reader = build_reader(pdf)
29
+ raise encrypted_error if reader.objects.encrypted?
30
+
31
+ document = new(reader)
32
+ document.validate!
33
+ raise signed_error if reject_signed && document.signed?
34
+
35
+ document
36
+ end
37
+
38
+ def build_reader(pdf)
39
+ PDF::Reader.new(StringIO.new(pdf.b))
40
+ rescue PDF::Reader::EncryptedPDFError
41
+ raise encrypted_error
42
+ end
43
+
44
+ def encrypted_error
45
+ ProtectedPdfError.new('Encrypted PDFs are not supported', protection: :encryption)
46
+ end
47
+
48
+ def signed_error
49
+ ProtectedPdfError.new('Signed PDFs are not supported', protection: :signature)
50
+ end
51
+
52
+ def invalid_error(error)
53
+ InvalidPdfError.new('Invalid PDF', reason: :malformed, cause: error.class.name)
54
+ end
55
+ end
56
+
57
+ def initialize(reader)
58
+ @reader = reader
59
+ @catalog = reader.objects.deref_hash(reader.objects.trailer[:Root])
60
+ end
61
+
62
+ def objects
63
+ reader.objects
64
+ end
65
+
66
+ def page_count
67
+ reader.page_count
68
+ end
69
+
70
+ def metadata
71
+ stream = objects.deref_stream(catalog[:Metadata])
72
+ return unless stream
73
+
74
+ stream.unfiltered_data.dup.force_encoding(Encoding::BINARY)
75
+ rescue PDF::Reader::MalformedPDFError, PDF::Reader::UnsupportedFeatureError
76
+ nil
77
+ end
78
+
79
+ def validate!
80
+ raise PDF::Reader::MalformedPDFError, 'PDF catalog is missing' unless catalog
81
+
82
+ reader.page_count
83
+ end
84
+
85
+ def signed?
86
+ return true if catalog.key?(:Perms)
87
+
88
+ objects.any? do |_reference, object|
89
+ dictionary = object.is_a?(PDF::Reader::Stream) ? object.hash : object
90
+ signature_dictionary?(dictionary)
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def signature_dictionary?(dictionary)
97
+ return false unless dictionary.is_a?(Hash)
98
+
99
+ dictionary[:Type] == :Sig || (dictionary.key?(:ByteRange) && dictionary.key?(:Contents))
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'document'
4
+ require_relative 'name_tree'
5
+ require_relative '../embedding'
6
+
7
+ module Facturx
8
+ module Pdf
9
+ class Extractor
10
+ Result = Data.define(:xml, :filename, :relationship, :metadata, :page_count)
11
+ Candidate = Data.define(:file_specification, :names)
12
+
13
+ def call(pdf)
14
+ document = Document.open(pdf)
15
+ build_result(document, select_candidate(document))
16
+ rescue InvalidPdfError, ExtractionError
17
+ raise
18
+ rescue PDF::Reader::MalformedPDFError => e
19
+ raise InvalidPdfError.new('Invalid PDF', reason: :malformed, cause: e.class.name)
20
+ rescue PDF::Reader::UnsupportedFeatureError => e
21
+ raise ExtractionError.new('Unable to decode the Factur-X attachment', reason: :unsupported_stream,
22
+ cause: e.class.name)
23
+ end
24
+
25
+ private
26
+
27
+ def build_result(document, candidate)
28
+ file_specification = candidate.file_specification
29
+ validate_names!(candidate.names)
30
+ validate_relationship!(file_specification)
31
+
32
+ Result.new(xml: embedded_xml(document, file_specification),
33
+ filename: FACTURX_EMBEDDING.filename,
34
+ relationship: FACTURX_EMBEDDING.relationship,
35
+ metadata: document.metadata,
36
+ page_count: document.page_count)
37
+ end
38
+
39
+ def select_candidate(document)
40
+ matches = matching_candidates(associated_file_candidates(document))
41
+ matches = matching_candidates(name_tree_candidates(document)) if matches.empty?
42
+
43
+ raise ExtractionError.new('Factur-X XML attachment is missing', reason: :missing_attachment) if matches.empty?
44
+ if matches.length > 1
45
+ raise ExtractionError.new('Factur-X XML attachment is ambiguous', reason: :ambiguous_attachment,
46
+ count: matches.length)
47
+ end
48
+
49
+ matches.first
50
+ end
51
+
52
+ def matching_candidates(candidates)
53
+ candidates.select { |candidate| candidate.names.include?(FACTURX_EMBEDDING.filename) }
54
+ end
55
+
56
+ def associated_file_candidates(document)
57
+ references = document.objects.deref_array(document.catalog[:AF]) || []
58
+ references.filter_map { |reference| candidate_from(document, reference) }
59
+ end
60
+
61
+ def name_tree_candidates(document)
62
+ NameTree.new(document).call.filter_map do |tree_name, file_specification|
63
+ candidate_from(document, file_specification, tree_name)
64
+ end
65
+ end
66
+
67
+ def candidate_from(document, reference, tree_name = nil)
68
+ file_specification = document.objects.deref_hash(reference)
69
+ return unless file_specification
70
+
71
+ names = [tree_name, file_specification[:F], file_specification[:UF]]
72
+ .compact
73
+ .map { |name| normalize_name(document, name) }
74
+ Candidate.new(file_specification:, names:)
75
+ end
76
+
77
+ def normalize_name(document, name)
78
+ value = document.objects.deref(name)
79
+ return value unless value.is_a?(String)
80
+ return value unless value.byteslice(0, 2) == "\xFE\xFF".b
81
+
82
+ value.byteslice(2..).force_encoding(Encoding::UTF_16BE).encode(Encoding::UTF_8)
83
+ rescue EncodingError
84
+ raise ExtractionError.new('Factur-X attachment has an invalid filename encoding',
85
+ reason: :invalid_filename_encoding)
86
+ end
87
+
88
+ def validate_names!(names)
89
+ return if names.all?(FACTURX_EMBEDDING.filename)
90
+
91
+ raise ExtractionError.new('Factur-X attachment has an invalid filename', reason: :invalid_filename,
92
+ filenames: names)
93
+ end
94
+
95
+ def validate_relationship!(file_specification)
96
+ return if file_specification[:AFRelationship] == FACTURX_EMBEDDING.relationship
97
+
98
+ raise ExtractionError.new('Factur-X attachment must use the Alternative relationship',
99
+ reason: :invalid_relationship,
100
+ relationship: file_specification[:AFRelationship])
101
+ end
102
+
103
+ def embedded_xml(document, file_specification)
104
+ embedded_files = document.objects.deref_hash(file_specification[:EF])
105
+ stream_reference = embedded_files && (embedded_files[:UF] || embedded_files[:F])
106
+ stream = document.objects.deref(stream_reference)
107
+ unless stream.is_a?(PDF::Reader::Stream)
108
+ raise ExtractionError.new('Factur-X attachment stream is missing', reason: :missing_stream)
109
+ end
110
+
111
+ stream.unfiltered_data.dup.force_encoding(Encoding::BINARY)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'document'
4
+
5
+ module Facturx
6
+ module Pdf
7
+ class Inspector
8
+ Result = Data.define(:page_count)
9
+
10
+ def call(pdf)
11
+ document = Document.open(pdf, reject_signed: true)
12
+
13
+ Result.new(page_count: document.page_count)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ module Pdf
5
+ class NameTree
6
+ def initialize(document)
7
+ @document = document
8
+ end
9
+
10
+ def call
11
+ names = objects.deref_hash(document.catalog[:Names])
12
+ root = names && names[:EmbeddedFiles]
13
+ root ? collect(root, {}) : []
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :document
19
+
20
+ def objects
21
+ document.objects
22
+ end
23
+
24
+ def collect(reference, visited)
25
+ key = reference_key(reference)
26
+ visit!(visited, key)
27
+ node = objects.deref_hash(reference) || invalid_tree!
28
+
29
+ entries(node[:Names]) + children(node).flat_map { |child| collect(child, visited) }
30
+ ensure
31
+ visited.delete(key)
32
+ end
33
+
34
+ def entries(reference)
35
+ return [] unless reference
36
+
37
+ names = objects.deref_array(reference)
38
+ invalid_tree! unless names&.length&.even?
39
+ names.each_slice(2).map { |tree_name, file_specification| [tree_name, file_specification] }
40
+ end
41
+
42
+ def children(node)
43
+ objects.deref_array(node[:Kids]) || []
44
+ end
45
+
46
+ def visit!(visited, key)
47
+ invalid_tree!('Embedded files name tree contains a cycle') if visited.key?(key)
48
+ visited[key] = true
49
+ end
50
+
51
+ def reference_key(reference)
52
+ reference.is_a?(PDF::Reader::Reference) ? [reference.id, reference.gen] : reference.object_id
53
+ end
54
+
55
+ def invalid_tree!(message = 'Embedded files name tree is invalid')
56
+ raise ExtractionError.new(message, reason: :invalid_name_tree)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'digest'
5
+
6
+ require_relative '../embedding'
7
+ require_relative '../error'
8
+
9
+ module Facturx
10
+ module Pdf
11
+ class Verifier
12
+ PDFA_NAMESPACE = 'http://www.aiim.org/pdfa/ns/id/'
13
+ RDF_NAMESPACE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
14
+ XMP_META_NAMESPACE = 'adobe:ns:meta/'
15
+ XMP_NAMESPACES = {
16
+ 'fx' => FACTURX_EMBEDDING.xmp_namespace,
17
+ 'pdfaid' => PDFA_NAMESPACE,
18
+ 'rdf' => RDF_NAMESPACE,
19
+ 'x' => XMP_META_NAMESPACE
20
+ }.freeze
21
+ DESCRIPTION_XPATH = '/x:xmpmeta/rdf:RDF/rdf:Description'
22
+ XMPField = Data.define(:xpath, :expected)
23
+ XMP_FIELDS = {
24
+ pdfa_part: XMPField.new(
25
+ xpath: "#{DESCRIPTION_XPATH}/pdfaid:part | " \
26
+ "#{DESCRIPTION_XPATH}/@pdfaid:part",
27
+ expected: '3'
28
+ ),
29
+ pdfa_conformance: XMPField.new(
30
+ xpath: "#{DESCRIPTION_XPATH}/pdfaid:conformance | " \
31
+ "#{DESCRIPTION_XPATH}/@pdfaid:conformance",
32
+ expected: 'B'
33
+ ),
34
+ document_filename: XMPField.new(
35
+ xpath: "#{DESCRIPTION_XPATH}/fx:DocumentFileName", expected: FACTURX_EMBEDDING.filename
36
+ ),
37
+ document_type: XMPField.new(
38
+ xpath: "#{DESCRIPTION_XPATH}/fx:DocumentType", expected: FACTURX_EMBEDDING.document_type
39
+ ),
40
+ facturx_version: XMPField.new(
41
+ xpath: "#{DESCRIPTION_XPATH}/fx:Version", expected: FACTURX_EMBEDDING.version
42
+ )
43
+ }.freeze
44
+ CONFORMANCE_LEVEL_XPATH = "#{DESCRIPTION_XPATH}/fx:ConformanceLevel".freeze
45
+ private_constant :XMPField
46
+
47
+ def call(result:, expected_xml:, expected_page_count:, profile:)
48
+ verify_result!(result, expected_xml, expected_page_count)
49
+ xmp = parse_xmp(result.metadata)
50
+ verify_xmp!(xmp, profile)
51
+ end
52
+
53
+ private
54
+
55
+ def verify_xmp!(xmp, profile)
56
+ XMP_FIELDS.each do |field, definition|
57
+ verify_xmp_value!(xmp, field, definition.xpath, definition.expected)
58
+ end
59
+ verify_xmp_value!(xmp, :facturx_conformance_level, CONFORMANCE_LEVEL_XPATH, profile.conformance_level)
60
+ end
61
+
62
+ def verify_result!(result, expected_xml, expected_page_count)
63
+ verify_xml!(result.xml, expected_xml)
64
+ verify_value!(:filename, result.filename, FACTURX_EMBEDDING.filename)
65
+ verify_value!(:relationship, result.relationship, FACTURX_EMBEDDING.relationship)
66
+ verify_value!(:page_count, result.page_count, expected_page_count)
67
+ end
68
+
69
+ def verify_xml!(actual, expected)
70
+ unless actual.is_a?(String) && expected.is_a?(String)
71
+ raise VerificationError.new('PDF verification input is invalid', reason: :invalid_input, field: :xml)
72
+ end
73
+ return if actual.b == expected.b
74
+
75
+ raise VerificationError.new('Composed PDF does not contain the requested XML bytes',
76
+ reason: :result_mismatch, field: :xml, **xml_details(actual, expected))
77
+ end
78
+
79
+ def xml_details(actual, expected)
80
+ {
81
+ expected_sha256: Digest::SHA256.hexdigest(expected),
82
+ actual_sha256: Digest::SHA256.hexdigest(actual),
83
+ expected_size: expected.bytesize,
84
+ actual_size: actual.bytesize
85
+ }
86
+ end
87
+
88
+ def parse_xmp(metadata)
89
+ raise VerificationError.new('PDF XMP metadata is missing', reason: :missing_xmp) unless metadata.is_a?(String)
90
+
91
+ Nokogiri::XML(metadata) { |config| config.strict.nonet }
92
+ rescue Nokogiri::XML::SyntaxError => e
93
+ raise VerificationError.new('PDF XMP metadata is invalid', reason: :invalid_xmp, cause: e.class.name)
94
+ end
95
+
96
+ def verify_xmp_value!(xmp, field, xpath, expected)
97
+ values = xmp.xpath(xpath, XMP_NAMESPACES).map(&:text)
98
+ return if values.one? && values.first == expected
99
+
100
+ raise VerificationError.new('PDF XMP metadata does not match Factur-X requirements',
101
+ reason: :xmp_mismatch, field:, expected:, actual: xmp_value(values))
102
+ end
103
+
104
+ def xmp_value(values)
105
+ values.one? ? values.first : values
106
+ end
107
+
108
+ def verify_value!(field, actual, expected)
109
+ return if actual == expected
110
+
111
+ raise VerificationError.new('Composed PDF does not match the requested Factur-X document',
112
+ reason: :result_mismatch, field:, expected:, actual:)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ Profile = Data.define(:id, :guideline_urn, :xsd_path, :conformance_level)
5
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'profile'
4
+
5
+ module Facturx
6
+ module Profiles
7
+ SPECIFICATION_VERSION = '1.09.2'
8
+ SCHEMA_ROOT = File.expand_path("schema/#{SPECIFICATION_VERSION}", __dir__).freeze
9
+
10
+ ALL = [
11
+ Profile.new(
12
+ id: :minimum,
13
+ guideline_urn: 'urn:factur-x.eu:1p0:minimum',
14
+ xsd_path: File.join(SCHEMA_ROOT, 'minimum/Factur-X_1.09.2_MINIMUM.xsd').freeze,
15
+ conformance_level: 'MINIMUM'
16
+ ),
17
+ Profile.new(
18
+ id: :basic_wl,
19
+ guideline_urn: 'urn:factur-x.eu:1p0:basicwl',
20
+ xsd_path: File.join(SCHEMA_ROOT, 'basic-wl/Factur-X_1.09.2_BASICWL.xsd').freeze,
21
+ conformance_level: 'BASIC WL'
22
+ ),
23
+ Profile.new(
24
+ id: :basic,
25
+ guideline_urn: 'urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic',
26
+ xsd_path: File.join(SCHEMA_ROOT, 'basic/Factur-X_1.09.2_BASIC.xsd').freeze,
27
+ conformance_level: 'BASIC'
28
+ ),
29
+ Profile.new(
30
+ id: :en16931,
31
+ guideline_urn: 'urn:cen.eu:en16931:2017',
32
+ xsd_path: File.join(SCHEMA_ROOT, 'en16931/Factur-X_1.09.2_EN16931.xsd').freeze,
33
+ conformance_level: 'EN 16931'
34
+ ),
35
+ Profile.new(
36
+ id: :extended,
37
+ guideline_urn: 'urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended',
38
+ xsd_path: File.join(SCHEMA_ROOT, 'extended/Factur-X_1.09.2_EXTENDED.xsd').freeze,
39
+ conformance_level: 'EXTENDED'
40
+ )
41
+ ].freeze
42
+
43
+ BY_ID = ALL.to_h { |profile| [profile.id, profile] }.freeze
44
+ BY_GUIDELINE_URN = ALL.to_h { |profile| [profile.guideline_urn, profile] }.freeze
45
+
46
+ module_function
47
+
48
+ def all
49
+ ALL
50
+ end
51
+
52
+ def fetch(id)
53
+ BY_ID.fetch(id.to_sym)
54
+ end
55
+
56
+ def for_guideline_urn(guideline_urn)
57
+ BY_GUIDELINE_URN[guideline_urn]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
3
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
4
+ xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
5
+ xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
6
+ xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
7
+ targetNamespace="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
8
+ elementFormDefault="qualified">
9
+ <xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_1.09.2_BASIC_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
10
+ <xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="ReusableAggregateBusinessInformationEntity_100.xsd"/>
11
+ <xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_1.09.2_BASIC_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
12
+ <xs:element name="CrossIndustryInvoice" type="rsm:CrossIndustryInvoiceType"/>
13
+ <xs:complexType name="CrossIndustryInvoiceType">
14
+ <xs:sequence>
15
+ <xs:element name="ExchangedDocumentContext" type="ram:ExchangedDocumentContextType"/>
16
+ <xs:element name="ExchangedDocument" type="ram:ExchangedDocumentType"/>
17
+ <xs:element name="SupplyChainTradeTransaction" type="ram:SupplyChainTradeTransactionType"/>
18
+ </xs:sequence>
19
+ </xs:complexType>
20
+ </xs:schema>
@@ -0,0 +1,86 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
3
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
4
+ targetNamespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
5
+ elementFormDefault="qualified">
6
+ <xs:simpleType name="AllowanceChargeReasonCodeContentType">
7
+ <xs:restriction base="xs:token"/>
8
+ </xs:simpleType>
9
+ <xs:complexType name="AllowanceChargeReasonCodeType">
10
+ <xs:simpleContent>
11
+ <xs:extension base="qdt:AllowanceChargeReasonCodeContentType"/>
12
+ </xs:simpleContent>
13
+ </xs:complexType>
14
+ <xs:simpleType name="CountryIDContentType">
15
+ <xs:restriction base="xs:token"/>
16
+ </xs:simpleType>
17
+ <xs:complexType name="CountryIDType">
18
+ <xs:simpleContent>
19
+ <xs:extension base="qdt:CountryIDContentType"/>
20
+ </xs:simpleContent>
21
+ </xs:complexType>
22
+ <xs:simpleType name="CurrencyCodeContentType">
23
+ <xs:restriction base="xs:token"/>
24
+ </xs:simpleType>
25
+ <xs:complexType name="CurrencyCodeType">
26
+ <xs:simpleContent>
27
+ <xs:extension base="qdt:CurrencyCodeContentType"/>
28
+ </xs:simpleContent>
29
+ </xs:complexType>
30
+ <xs:simpleType name="DocumentCodeContentType">
31
+ <xs:restriction base="xs:token"/>
32
+ </xs:simpleType>
33
+ <xs:complexType name="DocumentCodeType">
34
+ <xs:simpleContent>
35
+ <xs:extension base="qdt:DocumentCodeContentType"/>
36
+ </xs:simpleContent>
37
+ </xs:complexType>
38
+ <xs:simpleType name="FormattedDateTimeFormatContentType">
39
+ <xs:restriction base="xs:string"/>
40
+ </xs:simpleType>
41
+ <xs:complexType name="FormattedDateTimeType">
42
+ <xs:sequence>
43
+ <xs:element name="DateTimeString">
44
+ <xs:complexType>
45
+ <xs:simpleContent>
46
+ <xs:extension base="xs:string">
47
+ <xs:attribute name="format" type="qdt:FormattedDateTimeFormatContentType" use="required"/>
48
+ </xs:extension>
49
+ </xs:simpleContent>
50
+ </xs:complexType>
51
+ </xs:element>
52
+ </xs:sequence>
53
+ </xs:complexType>
54
+ <xs:simpleType name="PaymentMeansCodeContentType">
55
+ <xs:restriction base="xs:token"/>
56
+ </xs:simpleType>
57
+ <xs:complexType name="PaymentMeansCodeType">
58
+ <xs:simpleContent>
59
+ <xs:extension base="qdt:PaymentMeansCodeContentType"/>
60
+ </xs:simpleContent>
61
+ </xs:complexType>
62
+ <xs:simpleType name="TaxCategoryCodeContentType">
63
+ <xs:restriction base="xs:token"/>
64
+ </xs:simpleType>
65
+ <xs:complexType name="TaxCategoryCodeType">
66
+ <xs:simpleContent>
67
+ <xs:extension base="qdt:TaxCategoryCodeContentType"/>
68
+ </xs:simpleContent>
69
+ </xs:complexType>
70
+ <xs:simpleType name="TaxTypeCodeContentType">
71
+ <xs:restriction base="xs:token"/>
72
+ </xs:simpleType>
73
+ <xs:complexType name="TaxTypeCodeType">
74
+ <xs:simpleContent>
75
+ <xs:extension base="qdt:TaxTypeCodeContentType"/>
76
+ </xs:simpleContent>
77
+ </xs:complexType>
78
+ <xs:simpleType name="TimeReferenceCodeContentType">
79
+ <xs:restriction base="xs:token"/>
80
+ </xs:simpleType>
81
+ <xs:complexType name="TimeReferenceCodeType">
82
+ <xs:simpleContent>
83
+ <xs:extension base="qdt:TimeReferenceCodeContentType"/>
84
+ </xs:simpleContent>
85
+ </xs:complexType>
86
+ </xs:schema>
@@ -0,0 +1,60 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
3
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
4
+ targetNamespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
5
+ elementFormDefault="qualified">
6
+ <xs:complexType name="AmountType">
7
+ <xs:simpleContent>
8
+ <xs:extension base="xs:decimal">
9
+ <xs:attribute name="currencyID" type="xs:token"/>
10
+ </xs:extension>
11
+ </xs:simpleContent>
12
+ </xs:complexType>
13
+ <xs:complexType name="CodeType">
14
+ <xs:simpleContent>
15
+ <xs:extension base="xs:token"/>
16
+ </xs:simpleContent>
17
+ </xs:complexType>
18
+ <xs:complexType name="DateTimeType">
19
+ <xs:choice>
20
+ <xs:element name="DateTimeString">
21
+ <xs:complexType>
22
+ <xs:simpleContent>
23
+ <xs:extension base="xs:string">
24
+ <xs:attribute name="format" type="xs:string" use="required"/>
25
+ </xs:extension>
26
+ </xs:simpleContent>
27
+ </xs:complexType>
28
+ </xs:element>
29
+ </xs:choice>
30
+ </xs:complexType>
31
+ <xs:complexType name="IDType">
32
+ <xs:simpleContent>
33
+ <xs:extension base="xs:token">
34
+ <xs:attribute name="schemeID" type="xs:token"/>
35
+ </xs:extension>
36
+ </xs:simpleContent>
37
+ </xs:complexType>
38
+ <xs:complexType name="IndicatorType">
39
+ <xs:choice>
40
+ <xs:element name="Indicator" type="xs:boolean"/>
41
+ </xs:choice>
42
+ </xs:complexType>
43
+ <xs:complexType name="PercentType">
44
+ <xs:simpleContent>
45
+ <xs:extension base="xs:decimal"/>
46
+ </xs:simpleContent>
47
+ </xs:complexType>
48
+ <xs:complexType name="QuantityType">
49
+ <xs:simpleContent>
50
+ <xs:extension base="xs:decimal">
51
+ <xs:attribute name="unitCode" type="xs:token"/>
52
+ </xs:extension>
53
+ </xs:simpleContent>
54
+ </xs:complexType>
55
+ <xs:complexType name="TextType">
56
+ <xs:simpleContent>
57
+ <xs:extension base="xs:string"/>
58
+ </xs:simpleContent>
59
+ </xs:complexType>
60
+ </xs:schema>