Class: Uniword::Validation::SchemaRegistry
- Inherits:
-
Object
- Object
- Uniword::Validation::SchemaRegistry
- Defined in:
- lib/uniword/validation/schema_registry.rb
Overview
Maps XML namespace URIs to XSD schema files and loads schemas for validation.
Uses Moxml for namespace detection (parsing XML to find declared namespaces) and Nokogiri::XML::Schema for actual XSD validation.
The registry knows which OOXML namespace URIs correspond to which XSD files bundled in data/schemas/.
Constant Summary collapse
- NAMESPACE_XSD_MAP =
Map of namespace URI => XSD file (relative to data/schemas/)
{ # Base WordprocessingML (ISO 29500) "http://schemas.openxmlformats.org/wordprocessingml/2006/main" => "microsoft/wml-2010.xsd", # Microsoft versioned extensions "http://schemas.microsoft.com/office/word/2010/wordml" => "microsoft/wml-2010.xsd", "http://schemas.microsoft.com/office/word/2012/wordml" => "microsoft/wml-2012.xsd", "http://schemas.microsoft.com/office/word/2015/wordml/symex" => "microsoft/wml-symex-2015.xsd", "http://schemas.microsoft.com/office/word/2016/wordml/cid" => "microsoft/wml-cid-2016.xsd", "http://schemas.microsoft.com/office/word/2018/wordml" => "microsoft/wml-2018.xsd", "http://schemas.microsoft.com/office/word/2018/wordml/cex" => "microsoft/wml-cex-2018.xsd", "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" => "microsoft/wml-sdtdatahash-2020.xsd", # Markup Compatibility "http://schemas.openxmlformats.org/markup-compatibility/2006" => "mce/mc.xsd", # OPC schemas "http://schemas.openxmlformats.org/package/2006/content-types" => "ecma/opc-contentTypes.xsd", "http://schemas.openxmlformats.org/package/2006/relationships" => "ecma/opc-relationships.xsd", }.freeze
- WORDML_PARTS =
Parts that use WordprocessingML schemas
%w[ word/document.xml word/styles.xml word/settings.xml word/webSettings.xml word/fontTable.xml word/numbering.xml word/footnotes.xml word/endnotes.xml word/comments.xml ].freeze
- DOCPROPS_SCHEMAS =
Document property parts and their primary schemas.
{ "docProps/core.xml" => "ecma/opc-coreProperties.xsd", "docProps/app.xml" => "iso/shared-documentPropertiesExtended.xsd", "docProps/custom.xml" => "iso/shared-documentPropertiesCustom.xsd", }.freeze
- HEADER_FOOTER_PATTERN =
Pattern for header/footer parts
%r{\Aword/(header|footer)\d*\.xml\z}- THEME_PATTERN =
Pattern for theme parts
%r{\Aword/theme/theme\d+\.xml\z}- RELS_PATTERN =
Pattern for relationship parts
%r{_rels/.*\.rels\z}
Instance Attribute Summary collapse
-
#schemas_dir ⇒ Object
readonly
Returns the value of attribute schemas_dir.
Instance Method Summary collapse
-
#detect_ignorable(xml_content) ⇒ Array<String>
Detect mc:Ignorable prefixes from XML content.
-
#detect_namespaces(xml_content) ⇒ Array<String>
Detect namespace URIs from XML content.
-
#initialize(schemas_dir: nil) ⇒ SchemaRegistry
constructor
A new instance of SchemaRegistry.
-
#known_namespace?(uri) ⇒ Boolean
Check if a namespace URI has a known XSD schema.
-
#load_schema(xsd_relative_path) ⇒ Nokogiri::XML::Schema
Load and cache an XSD schema for validation.
-
#primary_schema_for_part(part_name) ⇒ String?
Determine the primary XSD schema for a given XML part.
-
#unknown_namespaces(ns_uris) ⇒ Array<String>
Return unknown namespace URIs from a set.
-
#xsd_map_for_namespaces(ns_uris) ⇒ Hash<String, String>
Map namespace URIs to their corresponding XSD files.
Constructor Details
#initialize(schemas_dir: nil) ⇒ SchemaRegistry
Returns a new instance of SchemaRegistry.
86 87 88 89 90 |
# File 'lib/uniword/validation/schema_registry.rb', line 86 def initialize(schemas_dir: nil) @schemas_dir = schemas_dir || default_schemas_dir @moxml = Moxml.new(:nokogiri) @schema_cache = {} end |
Instance Attribute Details
#schemas_dir ⇒ Object (readonly)
Returns the value of attribute schemas_dir.
84 85 86 |
# File 'lib/uniword/validation/schema_registry.rb', line 84 def schemas_dir @schemas_dir end |
Instance Method Details
#detect_ignorable(xml_content) ⇒ Array<String>
Detect mc:Ignorable prefixes from XML content.
108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/uniword/validation/schema_registry.rb', line 108 def detect_ignorable(xml_content) doc = @moxml.parse(xml_content) root = doc.root ignorable = root["Ignorable"] || root["mc:Ignorable"] return [] unless ignorable ignorable.split(/\s+/).reject(&:empty?) rescue StandardError => e Uniword.logger&.debug { "Ignorable detection failed: #{e.}" } [] end |
#detect_namespaces(xml_content) ⇒ Array<String>
Detect namespace URIs from XML content.
96 97 98 99 100 101 102 |
# File 'lib/uniword/validation/schema_registry.rb', line 96 def detect_namespaces(xml_content) doc = @moxml.parse(xml_content) doc.root.namespaces.map(&:uri) rescue StandardError => e Uniword.logger&.debug { "Namespace detection failed: #{e.}" } [] end |
#known_namespace?(uri) ⇒ Boolean
Check if a namespace URI has a known XSD schema.
192 193 194 |
# File 'lib/uniword/validation/schema_registry.rb', line 192 def known_namespace?(uri) NAMESPACE_XSD_MAP.key?(uri) end |
#load_schema(xsd_relative_path) ⇒ Nokogiri::XML::Schema
Load and cache an XSD schema for validation.
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 |
# File 'lib/uniword/validation/schema_registry.rb', line 149 def load_schema(xsd_relative_path) return @schema_cache[xsd_relative_path] if @schema_cache.key?(xsd_relative_path) xsd_path = File.join(schemas_dir, xsd_relative_path) unless File.exist?(xsd_path) raise ArgumentError, "XSD schema not found: #{xsd_path}" end # Nokogiri::XML::Schema resolves relative imports from CWD. # We chdir to the schema file's own directory so imports like # "word12.xsd" (from microsoft/wml-2010.xsd) resolve correctly. schema = nil original_dir = Dir.pwd schema_dir = File.dirname(xsd_path) begin Dir.chdir(schema_dir) schema = Nokogiri::XML::Schema(File.read(File.basename(xsd_path))) ensure Dir.chdir(original_dir) end @schema_cache[xsd_relative_path] = schema schema end |
#primary_schema_for_part(part_name) ⇒ String?
Determine the primary XSD schema for a given XML part.
For Word parts (document.xml, styles.xml, etc.), returns wml-2010.xsd which imports the base wml.xsd and all extension schemas. For relationship and content type parts, returns the appropriate schema. For docProps parts, returns the OPC/extended/custom properties schema.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/uniword/validation/schema_registry.rb', line 130 def primary_schema_for_part(part_name) case part_name when "[Content_Types].xml" "ecma/opc-contentTypes.xsd" when "_rels/.rels", ->(n) { n.match?(RELS_PATTERN) } "ecma/opc-relationships.xsd" when *DOCPROPS_SCHEMAS.keys DOCPROPS_SCHEMAS[part_name] when *WORDML_PARTS, ->(n) { n.match?(HEADER_FOOTER_PATTERN) } "microsoft/wml-2010.xsd" when ->(n) { n.match?(THEME_PATTERN) } "iso/dml-main.xsd" end end |
#unknown_namespaces(ns_uris) ⇒ Array<String>
Return unknown namespace URIs from a set.
200 201 202 |
# File 'lib/uniword/validation/schema_registry.rb', line 200 def unknown_namespaces(ns_uris) ns_uris.reject { |uri| known_namespace?(uri) } end |
#xsd_map_for_namespaces(ns_uris) ⇒ Hash<String, String>
Map namespace URIs to their corresponding XSD files.
179 180 181 182 183 184 185 186 |
# File 'lib/uniword/validation/schema_registry.rb', line 179 def xsd_map_for_namespaces(ns_uris) result = {} ns_uris.each do |uri| xsd = NAMESPACE_XSD_MAP[uri] result[uri] = xsd if xsd end result end |