diff --git a/README.md b/README.md index 0cf6c6a..115314c 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,13 @@ require "opencdd" db = Opencdd::Database.load("downloads/iec63213") # Find a class and walk its hierarchy -vehicle = db.find_by_code("AAA001") +vehicle = db.find_by_code("ORA001") puts vehicle.preferred_name # => "Vehicle" -puts vehicle.children.map(&:code) # => ["AAA010", "AAA020", "AAA030", ...] +puts vehicle.children.map(&:code) # => ["ORA010", "ORA020", "ORA030", ...] puts vehicle.effective_properties.map(&:code) # inherited + declared properties # Powertype API: what configuration options does EngineType offer? -engine_type = db.find_by_code("AAA200") +engine_type = db.find_by_code("ORA200") engine_type.powertype? # => true db.instances_of(engine_type).map(&:preferred_name) # => ["Single Diesel Engine", "Twin Diesel Engine", "Electric Hybrid Engine"] @@ -146,8 +146,8 @@ M2 Meta-model The meta-classes: Class, Property, Unit, ValueList, Modeled as Opencdd::MetaClass instances. Fixed set per the standard. -M1 Model The dictionary content: AAA001 "Vehicle", - AAAP001 "vehicle length", etc. Instances of M2 +M1 Model The dictionary content: ORA001 "Vehicle", + ORB001 "vehicle length", etc. Instances of M2 meta-classes. Modeled as Opencdd::Entity subclasses (Klass, Property, Unit, ...). diff --git a/docs/src/content/docs/architecture.md b/docs/src/content/docs/architecture.md index db52510..ae2b582 100644 --- a/docs/src/content/docs/architecture.md +++ b/docs/src/content/docs/architecture.md @@ -205,17 +205,17 @@ with semantic names (not wire-format keys): ```yaml --- -irdi: AAA001 +irdi: ORA001 type: class -code: AAA001 +code: ORA001 preferred_name: en: Vehicle fr: Véhicule class_type: ITEM_CLASS superclass: UNIVERSE applicable_properties: -- AAAP001 -- AAAP002 +- ORB001 +- ORB002 ``` Conversion between the YAML model and the existing Entity model is via diff --git a/docs/src/content/docs/cddal-syntax.md b/docs/src/content/docs/cddal-syntax.md index eb5532a..cf23bad 100644 --- a/docs/src/content/docs/cddal-syntax.md +++ b/docs/src/content/docs/cddal-syntax.md @@ -33,7 +33,7 @@ meta-class MDC_C002 { } instance Vehicle < MDC_C002 { - code: AAA001 + code: ORA001 preferred_name.en: "Vehicle" class_type: ITEM_CLASS } @@ -43,7 +43,7 @@ Parse it: ```ruby db = Opencdd::Cddal.parse(source) -db.find_by_code("AAA001").preferred_name # => "Vehicle" +db.find_by_code("ORA001").preferred_name # => "Vehicle" ``` ## Lexical structure @@ -86,7 +86,7 @@ Usually you don't write these — the runtime provides defaults. ```cddal instance Vehicle < MDC_C002 { - code: AAA001 + code: ORA001 preferred_name.en: "Vehicle" preferred_name.fr: "Véhicule" definition.en: "device that transports people or goods" @@ -108,7 +108,7 @@ Anonymous form (no `<`): ```cddal instance MDC_C002 { - code: AAA001 + code: ORA001 preferred_name.en: "Vehicle" } ``` @@ -149,7 +149,7 @@ Form: `[:.]: ` preferred_name.en: "Vehicle" # language-tagged (English) preferred_name.fr: "Véhicule" # language-tagged (French) superclass: Vehicle # symbolic reference -code: AAA001 # short code +code: ORA001 # short code data_type: REAL_TYPE # type identifier applicable_properties: { vehicle_length, vehicle_weight } condition: operating_mode == surface_water @@ -179,8 +179,8 @@ Three equivalent forms for references: ```cddal superclass: Vehicle # by symbolic name -superclass: AAA001 # by short code -superclass: 0112/2///61360_4#AAA001 # by full IRDI +superclass: ORA001 # by short code +superclass: 0112/2///OCEANRUNNER#ORA001 # by full IRDI ``` Resolution order: full IRDI → symbolic name → code. @@ -311,7 +311,7 @@ fetcher = Opencdd::Cddal::Fetcher::NetHttp.new(offline: true) Every entity created from CDDAL carries a `source_location`: ```ruby -entity = db.find_by_code("AAA001") +entity = db.find_by_code("ORA001") entity.source_location.file # => "/path/to/file.cddal" entity.source_location.line # => 42 ``` @@ -349,7 +349,7 @@ meta-class MDC_C002 { # 3. Instance declarations (the meat) instance Vehicle < MDC_C002 { - code: AAA001 + code: ORA001 preferred_name.en: "Vehicle" superclass: UNIVERSE class_type: ITEM_CLASS @@ -357,7 +357,7 @@ instance Vehicle < MDC_C002 { } instance vehicle_length < MDC_C003 { - code: AAAP001 + code: ORB001 preferred_name.en: "vehicle length" definition_class: Vehicle data_type: REAL_TYPE diff --git a/docs/src/content/docs/getting-started.md b/docs/src/content/docs/getting-started.md index f6c0319..a9a17b4 100644 --- a/docs/src/content/docs/getting-started.md +++ b/docs/src/content/docs/getting-started.md @@ -63,22 +63,22 @@ regardless of which format the data came from. ```ruby # By IRDI (canonical key) -db.find(Opencdd::IRDI.parse("0112/2///61360_4#AAA001")) +db.find(Opencdd::IRDI.parse("0112/2///OCEANRUNNER#ORA001")) # By short code (most common) -db.find_by_code("AAA001") # => # +db.find_by_code("ORA001") # => # # By preferred name -db.find_by_name("Boat", type: :class) # => # +db.find_by_name("Boat", type: :class) # => # ``` ### Type-partitioned accessors ```ruby -db.classes # => [#, #, ...] -db.properties # => [#, ...] +db.classes # => [#, #, ...] +db.properties # => [#, ...] db.units # => [#] -db.value_lists # => [#] +db.value_lists # => [#] db.value_terms # => [#] db.relations # => [#] db.view_controls # => [#] @@ -88,14 +88,14 @@ db.entities # => everything flat ### Class hierarchy ```ruby -vehicle = db.find_by_code("AAA001") +vehicle = db.find_by_code("ORA001") vehicle.parent_irdi # => nil (root) -vehicle.children.map(&:code) # => ["AAA010", "AAA020", "AAA030", "AAA100"] +vehicle.children.map(&:code) # => ["ORA010", "ORA020", "ORA030", "ORA100"] vehicle.ancestors # => [vehicle] (root) vehicle.descendants.map(&:code).size # => 19 -boat = db.find_by_code("AAA010") -boat.parent.code # => "AAA001" +boat = db.find_by_code("ORA010") +boat.parent.code # => "ORA001" boat.parent.preferred_name # => "Vehicle" ``` @@ -103,13 +103,13 @@ boat.parent.preferred_name # => "Vehicle" ```ruby vehicle.properties_on_class(db).map(&:code) # declared only -# => ["AAAP001", "AAAP002", "AAAP003"] +# => ["ORB001", "ORB002", "ORB003"] vehicle.effective_properties.map(&:code) # inherited + declared -# => ["AAAP001", "AAAP002", "AAAP003"] +# => ["ORB001", "ORB002", "ORB003"] # Walks superclass + is_case_of chains, cycle-safe -transmedium = db.find_by_code("AAA100") +transmedium = db.find_by_code("ORA100") transmedium.effective_properties.map(&:preferred_name).size # => 14 (3 own + 3 from Vehicle + 3 from Boat + 3 from Car + 2 from Submarine) ``` @@ -120,23 +120,23 @@ This is where CDD gets interesting. A `CATEGORICAL_CLASS` is a *class whose instances are themselves classes*. ```ruby -engine_type = db.find_by_code("AAA200") +engine_type = db.find_by_code("ORA200") engine_type.powertype? # => true # The powertype instances — EngineType's subclasses that ARE its # categorical options engine_type.categorical_instances(db).map(&:code) -# => ["AAA201", "AAA202", "AAA203"] # SingleDiesel, TwinDiesel, ElectricHybrid +# => ["ORA201", "ORA202", "ORA203"] # SingleDiesel, TwinDiesel, ElectricHybrid # Database-driven entry point (accepts Klass, IRDI, or code String) db.instances_of(engine_type).map(&:preferred_name) # => ["Single Diesel Engine", "Twin Diesel Engine", "Electric Hybrid Engine"] # Predicate: is this value a valid option for CLASS_REFERENCE(EngineType)? -single_diesel = db.find_by_code("AAA201") +single_diesel = db.find_by_code("ORA201") db.valid_class_reference?(engine_type, single_diesel) # => true -vehicle = db.find_by_code("AAA001") +vehicle = db.find_by_code("ORA001") db.valid_class_reference?(engine_type, vehicle) # => false ``` @@ -210,11 +210,11 @@ Opencdd::Exporters::Mermaid.new.to_diagram(db) # Mermaid markdown ```ruby errors = Opencdd::Validator.run(db) errors.first.rule # => "R08" -errors.first.entity_irdi.to_s # => "0112/2///61360_4#AAA001" +errors.first.entity_irdi.to_s # => "0112/2///OCEANRUNNER#ORA001" errors.first.message # => "R08: reference \"UNIVERSE\" does not resolve..." # Rule predicates — usable directly from the editor's live validator -Opencdd::Validator.irdi_well_formed?("AAA001") # => true +Opencdd::Validator.irdi_well_formed?("ORA001") # => true Opencdd::Validator.irdi_well_formed?("garbage") # => true (IRDI is permissive) Opencdd::Validator.class_hierarchy_acyclic?(db) # => true ``` @@ -239,7 +239,7 @@ import "./engines.cddal" as engines # qualified from "./colors.cddal" import { Red, Blue } # selective instance MyProduct < MDC_C002 { - code: AAA999 + code: ORA999 superclass: Vehicle engine_type: CLASS_REFERENCE(EngineType) color: Red diff --git a/docs/src/content/docs/model/yaml-persistence.md b/docs/src/content/docs/model/yaml-persistence.md index c8a1d44..cc7dd3a 100644 --- a/docs/src/content/docs/model/yaml-persistence.md +++ b/docs/src/content/docs/model/yaml-persistence.md @@ -29,9 +29,9 @@ translation_languages: - fr - ja entities: -- irdi: AAA001 +- irdi: ORA001 type: class - code: AAA001 + code: ORA001 preferred_name: en: Vehicle fr: Véhicule @@ -41,15 +41,15 @@ entities: class_type: ITEM_CLASS superclass: UNIVERSE applicable_properties: - - AAAP001 - - AAAP002 -- irdi: AAAP001 + - ORB001 + - ORB002 +- irdi: ORB001 type: property - code: AAAP001 + code: ORB001 preferred_name: en: vehicle length data_type: REAL_TYPE - definition_class: AAA001 + definition_class: ORA001 unit: metre property_data_element_type: NON_DEPENDENT_P_DET ``` @@ -143,7 +143,7 @@ db2 = Opencdd::Database.from_yaml(yaml) db.entities.size == db2.entities.size # true db.classes.size == db2.classes.size # true db.properties.size == db2.properties.size # true -db2.find_by_code("AAA200").powertype? # true +db2.find_by_code("ORA200").powertype? # true ``` The YAML round-trip preserves entity count, types, multilingual diff --git a/docs/src/content/docs/ontology.md b/docs/src/content/docs/ontology.md index d9b34d3..cd416b0 100644 --- a/docs/src/content/docs/ontology.md +++ b/docs/src/content/docs/ontology.md @@ -42,8 +42,8 @@ ISO/IEC 11179, but with a twist. │ M1 Model The dictionary content — instances of M2 │ │ ↓ meta-classes. This is what authors write. │ │ │ -│ AAA001 "Vehicle" (instance of Class) │ -│ AAAP001 "vehicle length" (instance of Property)│ +│ ORA001 "Vehicle" (instance of Class) │ +│ ORB001 "vehicle length" (instance of Property)│ │ UAC001 "metre" (instance of Unit) │ │ │ │ Modeled by: Opencdd::Entity subclasses │ @@ -79,24 +79,24 @@ Concretely, in the OceanRunner fixture: ```cddal instance EngineType < MDC_C002 { - code: AAA200 + code: ORA200 class_type: CATEGORICAL_CLASS } instance SingleDieselEngine < MDC_C002 { - code: AAA201 + code: ORA201 superclass: EngineType class_type: ITEM_CLASS } instance TwinDieselEngine < MDC_C002 { - code: AAA202 + code: ORA202 superclass: EngineType class_type: ITEM_CLASS } instance ElectricHybridEngine < MDC_C002 { - code: AAA203 + code: ORA203 superclass: EngineType class_type: ITEM_CLASS } @@ -114,7 +114,7 @@ IRDI as a *value* of a property whose type is `CLASS_REFERENCE(EngineType)`: ```cddal instance engine_type < MDC_C003 { - code: AAAP200 + code: ORB200 definition_class: OceanRunner data_type: CLASS_REFERENCE(EngineType) } @@ -125,7 +125,7 @@ categorical instances: ```cddal instance ORCA30_TwinDiesel_Premium_CarbonBlack < MDC_C002 { - code: BBB100 + code: ORA400 superclass: ORCA30 sub_class_selection: { TwinDieselEngine, PremiumInterior, CarbonBlackHull } } @@ -140,15 +140,15 @@ require "opencdd" db = Opencdd::Cddal.parse_file("reference-docs/examples/oceanrunner.cddal") -engine_type = db.find_by_code("AAA200") -single_diesel = db.find_by_code("AAA201") +engine_type = db.find_by_code("ORA200") +single_diesel = db.find_by_code("ORA201") # Predicate: is this a categorical (powertype) class? engine_type.powertype? # => true # What are its instances? engine_type.categorical_instances(db).map(&:code) -# => ["AAA201", "AAA202", "AAA203"] +# => ["ORA201", "ORA202", "ORA203"] # Or from the database side: db.instances_of(engine_type).map(&:preferred_name) @@ -156,7 +156,7 @@ db.instances_of(engine_type).map(&:preferred_name) # Validate a CLASS_REFERENCE value db.valid_class_reference?(engine_type, single_diesel) # => true -db.valid_class_reference?(engine_type, "AAA001") # => false (Vehicle isn't an EngineType instance) +db.valid_class_reference?(engine_type, "ORA001") # => false (Vehicle isn't an EngineType instance) ``` The R16 validator rule enforces `CLASS_REFERENCE` constraints: diff --git a/lib/opencdd/cddal/builder.rb b/lib/opencdd/cddal/builder.rb index ab53c12..cf853ae 100644 --- a/lib/opencdd/cddal/builder.rb +++ b/lib/opencdd/cddal/builder.rb @@ -177,7 +177,7 @@ def resolve_meta_class(ref) def build_properties(decl) # Resolve the meta-class once so per-assignment code-alias # resolution can be context-aware. Without this, a CDDAL - # `code: AAAP001` on a Property stores the value under + # `code: ORB001` on a Property stores the value under # MDC_P001_5 (the Class code) — Parcel readers look for # MDC_P001_6 (the Property code) and miss it, dropping the # entity on round-trip. @@ -225,7 +225,7 @@ def resolve_meta_class_code(ref) # canonical code property), and the resolved ID is one of the # MDC_P001_X code variants for a *different* meta-class, # we redirect to the canonical. This makes the CDDAL `code` - # alias context-aware: writing `code: AAA001` on a Property + # alias context-aware: writing `code: ORA001` on a Property # stores the value under MDC_P001_6, not MDC_P001_5. def resolve_property_id(name, canonical_code_pid: nil) s = name.to_s diff --git a/lib/opencdd/cddal/lexer.rb b/lib/opencdd/cddal/lexer.rb index 41b1d76..d0c063e 100644 --- a/lib/opencdd/cddal/lexer.rb +++ b/lib/opencdd/cddal/lexer.rb @@ -140,6 +140,15 @@ def scan_token end if ident = @ss.scan(IDENT_RE) + # BCP-47 language subtags in dotted keys (e.g. + # `preferred_name.zh-Hant:`) arrive directly after a DOT; + # join the hyphenated subtags into a single identifier. + # Hyphens elsewhere never adjoin IDENT-DOT-IDENT position, so + # this cannot merge arithmetic or reference tokens. + if @tokens.last&.kind == :DOT && (subtags = @ss.scan(/(?:-[A-Za-z][A-Za-z0-9]*)+/)) + ident += subtags + @col += subtags.length + end @col += ident.length kind = KEYWORD_MAP[ident] || :IDENT return Token.new(kind: kind, value: ident, line: line, column: col) diff --git a/lib/opencdd/exporters/json.rb b/lib/opencdd/exporters/json.rb index 88e9c22..56e17fa 100644 --- a/lib/opencdd/exporters/json.rb +++ b/lib/opencdd/exporters/json.rb @@ -108,11 +108,12 @@ def class_node(klass) end def property_node(prop) - entity_payload(prop).merge( - type: "property", - data_type: prop.parsed_data_type&.to_s, - value_list: value_list_irdi_of(prop), - ).compact + payload = entity_payload(prop) + # entity_payload may already carry data_type (string key) from + # the entity's own MDC_P022; a symbol key would coexist with it + # and JSON generation would reject the duplicate key. + payload["data_type"] ||= prop.parsed_data_type&.to_s + payload.merge(type: "property", value_list: value_list_irdi_of(prop)).compact end def unit_node(unit) diff --git a/lib/opencdd/meta_class.rb b/lib/opencdd/meta_class.rb index 39bb089..2df2803 100644 --- a/lib/opencdd/meta_class.rb +++ b/lib/opencdd/meta_class.rb @@ -7,7 +7,7 @@ module Opencdd # MDC_C003 Property, MDC_C009 Unit, etc. # Fixed set per the standard. Each is modeled # by an +Opencdd::MetaClass+ instance. - # M1 (model) — dictionary content: AAA001 Vehicle, AAAP001 + # M1 (model) — dictionary content: ORA001 Vehicle, ORB001 # vehicle_length, etc. Instances of M2 # meta-classes, modeled by Opencdd::Entity # subclasses (Klass, Property, Unit, ...). diff --git a/lib/opencdd/validator/class_reference_rule.rb b/lib/opencdd/validator/class_reference_rule.rb index 0864518..49f1909 100644 --- a/lib/opencdd/validator/class_reference_rule.rb +++ b/lib/opencdd/validator/class_reference_rule.rb @@ -17,7 +17,7 @@ module Validator # categorical constraint. # # Example: a Property `engine_type: CLASS_REFERENCE(EngineType)` - # whose value is "AAA001" (Vehicle) should fail — Vehicle is + # whose value is "ORA001" (Vehicle) should fail — Vehicle is # not a categorical instance of EngineType. Only SingleDiesel / # TwinDiesel / ElectricHybrid (EngineType's subclasses) pass. class ClassReferenceRule < Rule diff --git a/reference-docs/examples/antiques.cddal b/reference-docs/examples/antiques.cddal new file mode 100644 index 0000000..572d266 --- /dev/null +++ b/reference-docs/examples/antiques.cddal @@ -0,0 +1,909 @@ +# ============================================================================= +# East Asian Antiques 東方古董 — CDD worked example +# +# An auction-house-style reference dictionary for East Asian works of art, +# authored by OpenCDD from standard art-historical vocabulary. Demonstrates: +# +# * Three stacked classification dimensions as powertypes: +# AntiqueCategory (kind of object), Period (dynasty/era), +# Culture (country of origin) +# * Extended instances: each AntiqueCategory member is an instance +# selectable via the `category` property AND a class owning its own +# applicable_properties (e.g. Ceramics ⇒ kiln, glaze) +# * CONDITION_DET conditional property at the root: `reign_mark` applies +# only when category == Ceramics (the "Mark and Period" attribution +# of the auction world) +# * Configured subclasses via sub_class_selection: +# Ceramics × Ming ⇒ MingBlueAndWhite +# Ceramics × Goryeo ⇒ GoryeoCeladon +# (instance-of-instance used as a class) +# * Registered individuals: well-known museum objects as leaf entities +# with native multilingual names (zh-Hant, ja, ko) and English +# +# Reference basis for the category vocabulary (kept in the private repo): +# public department listings of international auction houses, used as +# reference only. All names, definitions and structure are our own. +# +# Code allocation (OpenCDD house convention): +# — stem is unique per dictionary, T is the entity type: +# A class · B property · E value list · T value term · I individual · U unit +# AN = antiques: ANA classes (incl. powertype members and configured +# subclasses), ANB properties, ANE enumerations, ANI museum objects. +# Blocks: 0xx culture · 02x period · 1xx object categories · 15x +# configured subclasses. +# ============================================================================= + +import "https://cdd.opencdd.org/dictionaries/rec20-units.cddal" + +# ----------------------------------------------------------------------------- +# Aliases (re-declare built-ins for reader clarity) +# ----------------------------------------------------------------------------- + +alias code: MDC_P001_5 +alias preferred_name: MDC_P004 +alias short_name: MDC_P005 +alias definition: MDC_P006 +alias note: MDC_P008 +alias remark: MDC_P009 +alias superclass: MDC_P010 +alias class_type: MDC_P011 +alias is_case_of: MDC_P013 +alias applicable_properties: MDC_P014 +alias property_data_element_type: MDC_P020 +alias definition_class: MDC_P021 +alias data_type: MDC_P022 +alias unit: MDC_P041 +alias condition: MDC_P028 +alias sub_class_selection: MDC_P016 +alias enumerated_values: MDC_P044 + +# ----------------------------------------------------------------------------- +# Meta-class schema +# ----------------------------------------------------------------------------- + +meta-class MDC_C002 { + code + preferred_name + short_name + definition + note + remark + synonym + superclass + class_type + is_case_of + applicable_properties + sub_class_selection + imported_properties +} + +meta-class MDC_C003 { + code + preferred_name + short_name + definition + note + remark + property_data_element_type + definition_class + data_type + unit + condition +} + +meta-class MDC_C005 { + code + preferred_name + definition + note + remark + enumerated_values +} + +# ============================================================================= +# Core categorical class: Antique +# ============================================================================= + +instance Antique < MDC_C002 { + code: ANA001 + preferred_name.en: "Antique" + preferred_name.zh-Hant: "古董" + preferred_name.ja: "骨董" + preferred_name.ko: "골동품" + definition.en: "object of art or craftsmanship from a past period, valued for its age, beauty, rarity, or provenance" + superclass: UNIVERSE + class_type: ITEM_CLASS + applicable_properties: { category, culture, period, height, diameter, condition_grade } +} + +# ============================================================================= +# Culture — plain instances (origin dimension) +# ============================================================================= + +instance Culture < MDC_C002 { + code: ANA010 + preferred_name.en: "Culture" + preferred_name.zh-Hant: "文化" + definition.en: "cultural sphere of origin of an antique" + superclass: Antique + class_type: CATEGORICAL_CLASS +} + +instance China < MDC_C002 { + code: ANA011 + preferred_name.en: "China" + preferred_name.zh-Hant: "中國" + definition.en: "Chinese cultural sphere of origin" + superclass: Culture + class_type: ITEM_CLASS +} + +instance Japan < MDC_C002 { + code: ANA012 + preferred_name.en: "Japan" + preferred_name.ja: "日本" + definition.en: "Japanese cultural sphere of origin" + superclass: Culture + class_type: ITEM_CLASS +} + +instance Korea < MDC_C002 { + code: ANA013 + preferred_name.en: "Korea" + preferred_name.ko: "한국" + definition.en: "Korean cultural sphere of origin" + superclass: Culture + class_type: ITEM_CLASS +} + +# ============================================================================= +# Powertype — Period (dynasty / era dimension) +# +# The second classification axis: the same object category exists in many +# periods, and a period is a value of the `period` property, not a subclass +# of the object tree. +# ============================================================================= + +instance Period < MDC_C002 { + code: ANA020 + preferred_name.en: "Period" + preferred_name.zh-Hant: "朝代時期" + definition.en: "categorical class whose members enumerate the dynasties and eras of East Asian art history" + superclass: Antique + class_type: CATEGORICAL_CLASS +} + +instance Shang < MDC_C002 { + code: ANA021 + preferred_name.en: "Shang" + preferred_name.zh-Hant: "商" + definition.en: "Chinese dynasty of the Bronze Age, c. 1600–1046 BC" + superclass: Period + class_type: ITEM_CLASS +} + +instance Zhou < MDC_C002 { + code: ANA022 + preferred_name.en: "Zhou" + preferred_name.zh-Hant: "周" + definition.en: "Chinese dynasty, 1046–256 BC, including the Western and Eastern Zhou" + superclass: Period + class_type: ITEM_CLASS +} + +instance Han < MDC_C002 { + code: ANA023 + preferred_name.en: "Han" + preferred_name.zh-Hant: "漢" + definition.en: "Chinese dynasty, 206 BC–AD 220" + superclass: Period + class_type: ITEM_CLASS +} + +instance Tang < MDC_C002 { + code: ANA024 + preferred_name.en: "Tang" + preferred_name.zh-Hant: "唐" + definition.en: "Chinese dynasty, 618–907" + superclass: Period + class_type: ITEM_CLASS +} + +instance Song < MDC_C002 { + code: ANA025 + preferred_name.en: "Song" + preferred_name.zh-Hant: "宋" + definition.en: "Chinese dynasty, 960–1279, including Northern and Southern Song" + superclass: Period + class_type: ITEM_CLASS +} + +instance Yuan < MDC_C002 { + code: ANA026 + preferred_name.en: "Yuan" + preferred_name.zh-Hant: "元" + definition.en: "Chinese dynasty, 1271–1368" + superclass: Period + class_type: ITEM_CLASS +} + +instance Ming < MDC_C002 { + code: ANA027 + preferred_name.en: "Ming" + preferred_name.zh-Hant: "明" + definition.en: "Chinese dynasty, 1368–1644" + superclass: Period + class_type: ITEM_CLASS +} + +instance Qing < MDC_C002 { + code: ANA028 + preferred_name.en: "Qing" + preferred_name.zh-Hant: "清" + definition.en: "Chinese dynasty, 1644–1912" + superclass: Period + class_type: ITEM_CLASS +} + +instance Goryeo < MDC_C002 { + code: ANA029 + preferred_name.en: "Goryeo" + preferred_name.ko: "고려" + definition.en: "Korean dynasty, 918–1392" + superclass: Period + class_type: ITEM_CLASS +} + +instance Joseon < MDC_C002 { + code: ANA030 + preferred_name.en: "Joseon" + preferred_name.ko: "조선" + definition.en: "Korean dynasty, 1392–1897" + superclass: Period + class_type: ITEM_CLASS +} + +instance Edo < MDC_C002 { + code: ANA031 + preferred_name.en: "Edo" + preferred_name.ja: "江戸" + definition.en: "Japanese era, 1603–1868" + superclass: Period + class_type: ITEM_CLASS +} + +instance Meiji < MDC_C002 { + code: ANA032 + preferred_name.en: "Meiji" + preferred_name.ja: "明治" + definition.en: "Japanese era, 1868–1912" + superclass: Period + class_type: ITEM_CLASS +} + +# ============================================================================= +# Powertype — AntiqueCategory (kind-of-object dimension) +# +# Each member is an EXTENDED INSTANCE: selectable as the value of the +# `category` CLASS_REFERENCE property AND a class in its own right that +# owns the properties specific to that kind of object. +# +# AntiqueCategory (CATEGORICAL_CLASS) +# ├── Ceramics ⇒ kiln, glaze (+ conditional reign_mark) +# ├── Bronze ⇒ vessel_form, inscription_characters +# ├── Jade ⇒ jade_stone +# ├── PaintingCalligraphy ⇒ medium, mounting_format +# ├── Furniture ⇒ wood_type +# ├── Lacquer ⇒ lacquer_technique +# ├── CloisonneEnamel ⇒ cloisonne_technique +# ├── BuddhistSculpture ⇒ sculpture_material +# ├── SnuffBottle ⇒ bottle_material +# └── UkiyoEPrint ⇒ print_format +# ============================================================================= + +instance AntiqueCategory < MDC_C002 { + code: ANA100 + preferred_name.en: "Antique Category" + preferred_name.zh-Hant: "品類" + definition.en: "categorical class whose members enumerate the kinds of East Asian works of art" + superclass: Antique + class_type: CATEGORICAL_CLASS +} + +instance Ceramics < MDC_C002 { + code: ANA101 + preferred_name.en: "Ceramics" + preferred_name.zh-Hant: "陶瓷" + preferred_name.ja: "陶磁器" + definition.en: "objects of fired clay, including porcelain and stoneware vessels and wares" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { kiln, glaze } +} + +instance Bronze < MDC_C002 { + code: ANA102 + preferred_name.en: "Bronze" + preferred_name.zh-Hant: "青銅器" + definition.en: "cast bronze ritual vessels, weapons and fittings" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { vessel_form, inscription_characters } +} + +instance Jade < MDC_C002 { + code: ANA103 + preferred_name.en: "Jade" + preferred_name.zh-Hant: "玉器" + definition.en: "objects carved from jade stone, whether nephrite or jadeite" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { jade_stone } +} + +instance PaintingCalligraphy < MDC_C002 { + code: ANA104 + preferred_name.en: "Painting and Calligraphy" + preferred_name.zh-Hant: "書畫" + preferred_name.ja: "書画" + definition.en: "paintings and works of calligraphy on paper or silk" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { medium, mounting_format } +} + +instance Furniture < MDC_C002 { + code: ANA105 + preferred_name.en: "Furniture" + preferred_name.zh-Hant: "家具" + definition.en: "cabinetwork and household furniture of hardwood or softwood" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { wood_type } +} + +instance Lacquer < MDC_C002 { + code: ANA106 + preferred_name.en: "Lacquer" + preferred_name.zh-Hant: "漆器" + preferred_name.ja: "漆工" + definition.en: "objects decorated or built in applied lacquer" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { lacquer_technique } +} + +instance CloisonneEnamel < MDC_C002 { + code: ANA107 + preferred_name.en: "Cloisonné Enamel" + preferred_name.zh-Hant: "掐絲琺瑯" + definition.en: "objects decorated with cloisonné enamel, wire-outlined glass paste fired on a metal body" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { cloisonne_technique } +} + +instance BuddhistSculpture < MDC_C002 { + code: ANA108 + preferred_name.en: "Buddhist Sculpture" + preferred_name.zh-Hant: "佛教造像" + definition.en: "sculptural images of Buddhist devotion" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { sculpture_material } +} + +instance SnuffBottle < MDC_C002 { + code: ANA109 + preferred_name.en: "Snuff Bottle" + preferred_name.zh-Hant: "鼻煙壺" + definition.en: "small bottle for powdered tobacco, made in glass, porcelain, jade and other materials" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { bottle_material } +} + +instance UkiyoEPrint < MDC_C002 { + code: ANA110 + preferred_name.en: "Ukiyo-e Print" + preferred_name.zh-Hant: "浮世繪" + preferred_name.ja: "浮世絵" + definition.en: "Japanese woodblock print of the floating-world genre" + superclass: AntiqueCategory + class_type: ITEM_CLASS + applicable_properties: { print_format } +} + +# ============================================================================= +# Enumerations +# ============================================================================= + +instance vessel_form_enum < MDC_C005 { + code: ANE001 + preferred_name.en: "vessel form categories" + definition.en: "enumeration of classical Chinese ritual vessel forms" + enumerated_values: { Ding, Gui, Jue, Hu, Zun } +} + +instance jade_stone_enum < MDC_C005 { + code: ANE002 + preferred_name.en: "jade stone categories" + definition.en: "enumeration of the jade minerals" + enumerated_values: { Nephrite, Jadeite } +} + +instance medium_enum < MDC_C005 { + code: ANE003 + preferred_name.en: "medium categories" + definition.en: "enumeration of painting and calligraphy media" + enumerated_values: { Ink_on_paper, Ink_on_silk, Ink_and_colour_on_paper, Ink_and_colour_on_silk } +} + +instance mounting_enum < MDC_C005 { + code: ANE004 + preferred_name.en: "mounting format categories" + definition.en: "enumeration of traditional East Asian mounting formats" + enumerated_values: { Handscroll, Hanging_scroll, Album_leaf, Fan } +} + +instance sculpture_material_enum < MDC_C005 { + code: ANE005 + preferred_name.en: "sculpture material categories" + definition.en: "enumeration of sculptural materials of Buddhist images" + enumerated_values: { Wood, Bronze, Gilt_bronze, Stone } +} + +instance print_format_enum < MDC_C005 { + code: ANE006 + preferred_name.en: "print format categories" + definition.en: "enumeration of Japanese woodblock print sheet formats" + enumerated_values: { Oban, Chuban, Aiban, Kakemono_e } +} + +instance condition_enum < MDC_C005 { + code: ANE007 + preferred_name.en: "condition grade categories" + definition.en: "enumeration of preservation condition grades used in cataloguing" + enumerated_values: { Excellent, Very_good, Good, Fair } +} + +# ============================================================================= +# Core properties (defined on Antique) +# ============================================================================= + +instance category < MDC_C003 { + code: ANB001 + preferred_name.en: "antique category" + preferred_name.zh-Hant: "品類" + definition.en: "kind of object, selected from the antique category classification" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Antique + data_type: CLASS_REFERENCE(AntiqueCategory) +} + +instance culture < MDC_C003 { + code: ANB002 + preferred_name.en: "culture" + preferred_name.zh-Hant: "文化" + definition.en: "cultural sphere of origin of the antique" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Antique + data_type: CLASS_REFERENCE(Culture) +} + +instance period < MDC_C003 { + code: ANB003 + preferred_name.en: "period" + preferred_name.zh-Hant: "朝代時期" + definition.en: "dynasty or era of the antique" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Antique + data_type: CLASS_REFERENCE(Period) +} + +instance height < MDC_C003 { + code: ANB004 + preferred_name.en: "height" + preferred_name.zh-Hant: "高度" + definition.en: "height of the object as catalogued" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Antique + data_type: REAL_TYPE + unit: centimetre +} + +instance diameter < MDC_C003 { + code: ANB005 + preferred_name.en: "diameter" + preferred_name.zh-Hant: "口徑" + definition.en: "diameter of the widest body section, as catalogued" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Antique + data_type: REAL_TYPE + unit: centimetre +} + +instance condition_grade < MDC_C003 { + code: ANB006 + preferred_name.en: "condition grade" + preferred_name.zh-Hant: "品相" + definition.en: "preservation condition of the object" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Antique + data_type: ENUM_STRING_TYPE(condition_enum) +} + +# Conditional attribution — the "Mark and Period" pattern: a reign mark is +# catalogued only for objects whose category selects Ceramics. +instance reign_mark < MDC_C003 { + code: ANB007 + preferred_name.en: "reign mark" + preferred_name.zh-Hant: "年款" + definition.en: "imperial reign mark inscribed on the object" + property_data_element_type: CONDITION_DET + definition_class: Antique + data_type: STRING_TYPE + condition: category == Ceramics +} + +# ============================================================================= +# Category-specific properties — defined ON the extended instances +# +# These properties exist only because their definition_class is an instance +# of a CATEGORICAL_CLASS: an instance that owns class-level facts. +# ============================================================================= + +instance kiln < MDC_C003 { + code: ANB101 + preferred_name.en: "kiln" + preferred_name.zh-Hant: "窯口" + definition.en: "kiln complex or firing centre of origin" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Ceramics + data_type: STRING_TYPE +} + +instance glaze < MDC_C003 { + code: ANB102 + preferred_name.en: "glaze" + preferred_name.zh-Hant: "釉彩" + definition.en: "glaze and dominant decorative palette of the ware" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Ceramics + data_type: STRING_TYPE +} + +instance vessel_form < MDC_C003 { + code: ANB103 + preferred_name.en: "vessel form" + preferred_name.zh-Hant: "器形" + definition.en: "classical ritual vessel form of the bronze" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Bronze + data_type: ENUM_STRING_TYPE(vessel_form_enum) +} + +instance inscription_characters < MDC_C003 { + code: ANB104 + preferred_name.en: "inscription character count" + preferred_name.zh-Hant: "銘文字數" + definition.en: "number of characters in the cast inscription" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Bronze + data_type: REAL_TYPE +} + +instance jade_stone < MDC_C003 { + code: ANB105 + preferred_name.en: "jade stone" + preferred_name.zh-Hant: "玉料" + definition.en: "jade mineral of the carved stone" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Jade + data_type: ENUM_STRING_TYPE(jade_stone_enum) +} + +instance medium < MDC_C003 { + code: ANB106 + preferred_name.en: "medium" + preferred_name.zh-Hant: "材質" + definition.en: "ink and colour medium on the paper or silk support" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: PaintingCalligraphy + data_type: ENUM_STRING_TYPE(medium_enum) +} + +instance mounting_format < MDC_C003 { + code: ANB107 + preferred_name.en: "mounting format" + preferred_name.zh-Hant: "裝裱形式" + definition.en: "traditional mounting format of the work" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: PaintingCalligraphy + data_type: ENUM_STRING_TYPE(mounting_enum) +} + +instance wood_type < MDC_C003 { + code: ANB108 + preferred_name.en: "wood type" + preferred_name.zh-Hant: "木料" + definition.en: "timber of the furniture" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Furniture + data_type: STRING_TYPE +} + +instance lacquer_technique < MDC_C003 { + code: ANB109 + preferred_name.en: "lacquer technique" + preferred_name.zh-Hant: "漆藝" + definition.en: "decorative lacquer technique of the object" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Lacquer + data_type: STRING_TYPE +} + +instance cloisonne_technique < MDC_C003 { + code: ANB110 + preferred_name.en: "cloisonné technique" + preferred_name.zh-Hant: "琺瑯工藝" + definition.en: "enamel technique applied to the metal body" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: CloisonneEnamel + data_type: STRING_TYPE +} + +instance sculpture_material < MDC_C003 { + code: ANB111 + preferred_name.en: "sculpture material" + preferred_name.zh-Hant: "造像材質" + definition.en: "material of the sculptural image" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: BuddhistSculpture + data_type: ENUM_STRING_TYPE(sculpture_material_enum) +} + +instance bottle_material < MDC_C003 { + code: ANB112 + preferred_name.en: "bottle material" + preferred_name.zh-Hant: "壺身材質" + definition.en: "material of the snuff bottle body" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: SnuffBottle + data_type: STRING_TYPE +} + +instance print_format < MDC_C003 { + code: ANB113 + preferred_name.en: "print format" + preferred_name.zh-Hant: "版式" + definition.en: "sheet format of the woodblock print" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: UkiyoEPrint + data_type: ENUM_STRING_TYPE(print_format_enum) +} + +# ============================================================================= +# Configured subclasses — instance-of-instance used as a class +# +# A ceramics category × period selection yields a cataloguing class whose +# instances inherit Antique's core properties, Ceramics' kiln and glaze, +# and the conditional reign_mark. +# ============================================================================= + +instance MingBlueAndWhite < MDC_C002 { + code: ANA150 + preferred_name.en: "Ming Blue-and-White Porcelain" + preferred_name.zh-Hant: "明青花" + definition.en: "porcelain of the Ming dynasty decorated in underglaze cobalt blue" + superclass: Ceramics + class_type: ITEM_CLASS + sub_class_selection: { Ming } +} + +instance GoryeoCeladon < MDC_C002 { + code: ANA151 + preferred_name.en: "Goryeo Celadon" + preferred_name.ko: "고려청자" + definition.en: "celadon-glazed stoneware of the Goryeo dynasty" + superclass: Ceramics + class_type: ITEM_CLASS + sub_class_selection: { Goryeo } +} + +# ============================================================================= +# Registered individuals — well-known museum objects (leaf entities) +# +# Each is a concrete object with a native-language name, an English +# cataloguing name, and property values, classified through the power-type +# chain: Antique ← AntiqueCategory member ← configured subclass. +# ============================================================================= + +instance MaoGongDing < MDC_C002 { + code: ANI001 + preferred_name.zh-Hant: "毛公鼎" + preferred_name.en: "Mao Gong Ding" + short_name.en: "MaoGongDing" + definition.en: "Western Zhou bronze tripod food vessel bearing one of the longest known Chinese bronze inscriptions, National Palace Museum, Taipei" + superclass: Bronze + class_type: ITEM_CLASS + category: Bronze + culture: China + period: Zhou + height: 53.8 + diameter: 47.9 + vessel_form: Ding + inscription_characters: 497 + condition_grade: Excellent +} + +instance JadeiteCabbage < MDC_C002 { + code: ANI002 + preferred_name.zh-Hant: "翠玉白菜" + preferred_name.en: "Jadeite Cabbage" + short_name.en: "JadeiteCabbage" + definition.en: "Qing dynasty jadeite carving of a Chinese cabbage with insects, National Palace Museum, Taipei" + superclass: Jade + class_type: ITEM_CLASS + category: Jade + culture: China + period: Qing + height: 18.7 + jade_stone: Jadeite + condition_grade: Excellent +} + +instance QingmingScroll < MDC_C002 { + code: ANI003 + preferred_name.zh-Hant: "清明上河圖" + preferred_name.en: "Along the River During the Qingming Festival" + short_name.en: "QingmingScroll" + definition.en: "attributed to Zhang Zeduan, Northern Song handscroll depicting the capital Bianjing, Palace Museum, Beijing" + superclass: PaintingCalligraphy + class_type: ITEM_CLASS + category: PaintingCalligraphy + culture: China + period: Song + height: 24.8 + medium: Ink_and_colour_on_silk + mounting_format: Handscroll + condition_grade: Good +} + +instance GoryeoMaebyeongVase < MDC_C002 { + code: ANI004 + preferred_name.ko: "고려청자 매병" + preferred_name.en: "Goryeo Celadon Maebyeong Vase" + short_name.en: "MaebyeongVase" + definition.en: "Goryeo dynasty celadon vase of plump maebyeong form, National Museum of Korea, Seoul" + superclass: GoryeoCeladon + class_type: ITEM_CLASS + category: Ceramics + culture: Korea + period: Goryeo + glaze: "celadon" + condition_grade: Very_good +} + +instance GreatWavePrint < MDC_C002 { + code: ANI005 + preferred_name.ja: "神奈川沖浪裏" + preferred_name.en: "The Great Wave off Kanagawa" + short_name.en: "GreatWave" + definition.en: "woodblock print by Katsushika Hokusai from the series Thirty-six Views of Mount Fuji" + superclass: UkiyoEPrint + class_type: ITEM_CLASS + category: UkiyoEPrint + culture: Japan + period: Edo + height: 25.4 + print_format: Oban + condition_grade: Good +} + +instance XuandeDragonJar < MDC_C002 { + code: ANI006 + preferred_name.zh-Hant: "明宣德青花穿花龍紋罐" + preferred_name.en: "Blue-and-white 'Interlocking Dragon' Jar, Xuande Mark and Period" + short_name.en: "XuandeJar" + definition.en: "Ming Xuande porcelain jar painted in underglaze blue with dragons among lotus scrolls, bearing the imperial reign mark" + superclass: MingBlueAndWhite + class_type: ITEM_CLASS + category: Ceramics + culture: China + period: Ming + kiln: "Jingdezhen" + glaze: "blue and white" + reign_mark: "Xuande (宣德)" + condition_grade: Very_good +} + +instance JoseonMoonJar < MDC_C002 { + code: ANI007 + preferred_name.ko: "달항아리" + preferred_name.en: "Joseon Moon Jar" + short_name.en: "MoonJar" + definition.en: "Joseon dynasty white-porcelain storage jar of large spherical 'moon' form" + superclass: Ceramics + class_type: ITEM_CLASS + category: Ceramics + culture: Korea + period: Joseon + glaze: "white porcelain" + condition_grade: Very_good +} + +instance TangGiltBronzeBuddha < MDC_C002 { + code: ANI008 + preferred_name.zh-Hant: "唐鎏金銅佛坐像" + preferred_name.en: "Tang Gilt-Bronze Seated Buddha" + short_name.en: "TangBuddha" + definition.en: "Tang dynasty gilt-bronze figure of the seated Buddha" + superclass: BuddhistSculpture + class_type: ITEM_CLASS + category: BuddhistSculpture + culture: China + period: Tang + sculpture_material: Gilt_bronze + condition_grade: Good +} + +instance HuanghualiCabinet < MDC_C002 { + code: ANI009 + preferred_name.zh-Hant: "明黃花梨圓角櫃" + preferred_name.en: "Ming Huanghuali Rounded-Corner Cabinet" + short_name.en: "HuanghualiCabinet" + definition.en: "Ming dynasty rounded-corner cabinet of huanghuali hardwood with original mounts" + superclass: Furniture + class_type: ITEM_CLASS + category: Furniture + culture: China + period: Ming + wood_type: "huanghuali" + condition_grade: Good +} + +instance InsidePaintedSnuffBottle < MDC_C002 { + code: ANI010 + preferred_name.zh-Hant: "玻璃內畫鼻煙壺" + preferred_name.en: "Inside-Painted Glass Snuff Bottle" + short_name.en: "SnuffBottle" + definition.en: "Qing dynasty snuff bottle of transparent glass painted on the interior surface" + superclass: SnuffBottle + class_type: ITEM_CLASS + category: SnuffBottle + culture: China + period: Qing + bottle_material: "glass" + condition_grade: Very_good +} + +instance CloisonneCenser < MDC_C002 { + code: ANI011 + preferred_name.zh-Hant: "掐絲琺瑯香爐" + preferred_name.en: "Cloisonné Enamel Censer" + short_name.en: "CloisonneCenser" + definition.en: "censer of copper decorated with wire-outlined enamel, of the type made for the Ming court" + superclass: CloisonneEnamel + class_type: ITEM_CLASS + category: CloisonneEnamel + culture: China + period: Ming + cloisonne_technique: "cloisonné wire enamel" + condition_grade: Good +} + +instance CarvedLacquerBox < MDC_C002 { + code: ANI012 + preferred_name.zh-Hant: "雕漆蓋盒" + preferred_name.en: "Carved Red Lacquer Box and Cover" + short_name.en: "LacquerBox" + definition.en: "box and cover deeply carved through thick red lacquer layers with floral scroll" + superclass: Lacquer + class_type: ITEM_CLASS + category: Lacquer + culture: China + period: Ming + lacquer_technique: "carved red lacquer" + condition_grade: Good +} diff --git a/reference-docs/examples/oceanrunner.cddal b/reference-docs/examples/oceanrunner.cddal index 121236d..93f485b 100644 --- a/reference-docs/examples/oceanrunner.cddal +++ b/reference-docs/examples/oceanrunner.cddal @@ -12,6 +12,16 @@ # # Brand: OceanRunner (submersible boat-car manufacturer) # Products: ORCA 30 (compact), ORCA 50 (full-size) +# +# Code allocation (OpenCDD house convention): +# — stem is unique per dictionary, T is the entity type: +# A class · B property · E value list · T value term · I individual · U unit +# OR = oceanrunner: ORA001 Vehicle … ORA200 EngineType (powertype), +# ORA300 OceanRunner (brand), ORA400 configured subclass, ORI0001 hull unit. +# Blocks: 0xx base hierarchy · 1xx transmedium · 2xx option types · +# 3xx brand & product lines · 4xx configured subclasses. +# IEC CDD allocates its own opaque blocks (AA, AB, AC, U, KE, KD, SA, C*) +# for its dictionaries; OpenCDD stems never collide with them. # ============================================================================= import "https://cdd.opencdd.org/dictionaries/rec20-units.cddal" @@ -97,25 +107,75 @@ meta-class MDC_C009 { # ============================================================================= # # UNIVERSE -# └── Vehicle (AAA001) -# ├── Boat (AAA010) -# ├── Car (AAA020) -# └── Submarine (AAA030) +# └── Vehicle (ORA001) +# ├── Boat (ORA010) +# ├── Car (ORA020) +# └── Submarine (ORA030) # ============================================================================= instance Vehicle < MDC_C002 { - code: AAA001 + code: ORA001 preferred_name.en: "Vehicle" preferred_name.fr: "Véhicule" preferred_name.ja: "車両" definition.en: "device that transports people or goods" superclass: UNIVERSE class_type: ITEM_CLASS - applicable_properties: { vehicle_length, vehicle_weight, vehicle_capacity } + applicable_properties: { vehicle_length, vehicle_beam, vehicle_height, vehicle_weight, vehicle_capacity, propulsion_power, operating_range, fuel_capacity } +} + +instance vehicle_beam < MDC_C003 { + code: ORB004 + preferred_name.en: "vehicle beam" + definition.en: "maximum width of the vehicle at its widest point" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Vehicle + data_type: REAL_TYPE + unit: metre +} + +instance vehicle_height < MDC_C003 { + code: ORB005 + preferred_name.en: "vehicle height" + definition.en: "height of the vehicle from waterline or ground to its highest fixed point" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Vehicle + data_type: REAL_TYPE + unit: metre +} + +instance propulsion_power < MDC_C003 { + code: ORB006 + preferred_name.en: "propulsion power" + definition.en: "combined rated power of all propulsion units" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Vehicle + data_type: REAL_TYPE + unit: kilowatt +} + +instance operating_range < MDC_C003 { + code: ORB007 + preferred_name.en: "operating range" + definition.en: "distance the vehicle can travel on one full load of fuel or energy" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Vehicle + data_type: REAL_TYPE + unit: nautical_mile +} + +instance fuel_capacity < MDC_C003 { + code: ORB008 + preferred_name.en: "fuel capacity" + definition.en: "total volume of fuel or energy carrier stored on board" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Vehicle + data_type: REAL_TYPE + unit: litre } instance vehicle_length < MDC_C003 { - code: AAAP001 + code: ORB001 preferred_name.en: "vehicle length" definition.en: "overall length of the vehicle, inclusive of all fixed structures" property_data_element_type: NON_DEPENDENT_P_DET @@ -125,7 +185,7 @@ instance vehicle_length < MDC_C003 { } instance vehicle_weight < MDC_C003 { - code: AAAP002 + code: ORB002 preferred_name.en: "vehicle weight" definition.en: "mass of the vehicle in ready-to-operate condition" property_data_element_type: NON_DEPENDENT_P_DET @@ -135,7 +195,7 @@ instance vehicle_weight < MDC_C003 { } instance vehicle_capacity < MDC_C003 { - code: AAAP003 + code: ORB003 preferred_name.en: "vehicle capacity" definition.en: "maximum number of occupants the vehicle is type-approved to carry" property_data_element_type: NON_DEPENDENT_P_DET @@ -148,16 +208,44 @@ instance vehicle_capacity < MDC_C003 { # ----------------------------------------------------------------------------- instance Boat < MDC_C002 { - code: AAA010 + code: ORA010 preferred_name.en: "Boat" definition.en: "vehicle that travels on the surface of water" superclass: Vehicle class_type: ITEM_CLASS - applicable_properties: { hull_length, water_displacement, max_speed_knots } + applicable_properties: { hull_length, draft, water_displacement, max_speed_knots, hull_form, berth_count } +} + +instance draft < MDC_C003 { + code: ORB013 + preferred_name.en: "draft" + definition.en: "depth of the hull below the waterline when fully laden" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Boat + data_type: REAL_TYPE + unit: metre +} + +instance berth_count < MDC_C003 { + code: ORB014 + preferred_name.en: "berth count" + definition.en: "number of sleeping berths fitted below deck" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Boat + data_type: INT_TYPE +} + +instance hull_form < MDC_C003 { + code: ORB015 + preferred_name.en: "hull form" + definition.en: "arrangement of hull bodies" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Boat + data_type: ENUM_STRING_TYPE(hull_form_enum) } instance hull_length < MDC_C003 { - code: AAAP010 + code: ORB010 preferred_name.en: "hull length" definition.en: "overall length of the hull measured at the design waterline" property_data_element_type: NON_DEPENDENT_P_DET @@ -167,7 +255,7 @@ instance hull_length < MDC_C003 { } instance water_displacement < MDC_C003 { - code: AAAP011 + code: ORB011 preferred_name.en: "water displacement" definition.en: "mass of water displaced by the hull at rest in fresh water" property_data_element_type: NON_DEPENDENT_P_DET @@ -177,7 +265,7 @@ instance water_displacement < MDC_C003 { } instance max_speed_knots < MDC_C003 { - code: AAAP012 + code: ORB012 preferred_name.en: "maximum speed in knots" definition.en: "maximum sustained speed on the water surface" property_data_element_type: NON_DEPENDENT_P_DET @@ -191,16 +279,54 @@ instance max_speed_knots < MDC_C003 { # ----------------------------------------------------------------------------- instance Car < MDC_C002 { - code: AAA020 + code: ORA020 preferred_name.en: "Car" definition.en: "vehicle that travels on paved roads" superclass: Vehicle class_type: ITEM_CLASS - applicable_properties: { wheelbase, tire_count, road_speed_max } + applicable_properties: { wheelbase, ground_clearance, turning_circle, tire_count, seating_capacity, drive_layout, road_speed_max } +} + +instance ground_clearance < MDC_C003 { + code: ORB023 + preferred_name.en: "ground clearance" + definition.en: "distance between the lowest fixed point of the vehicle and the road surface" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Car + data_type: REAL_TYPE + unit: metre +} + +instance seating_capacity < MDC_C003 { + code: ORB024 + preferred_name.en: "seating capacity" + definition.en: "number of seated occupants on the road" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Car + data_type: INT_TYPE +} + +instance drive_layout < MDC_C003 { + code: ORB025 + preferred_name.en: "drive layout" + definition.en: "which wheels are driven by the powertrain on the road" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Car + data_type: ENUM_STRING_TYPE(drive_layout_enum) +} + +instance turning_circle < MDC_C003 { + code: ORB026 + preferred_name.en: "turning circle" + definition.en: "diameter of the smallest full turn on a paved surface" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Car + data_type: REAL_TYPE + unit: metre } instance wheelbase < MDC_C003 { - code: AAAP020 + code: ORB020 preferred_name.en: "wheelbase" definition.en: "distance between the centres of the front and rear wheel contact patches" property_data_element_type: NON_DEPENDENT_P_DET @@ -210,7 +336,7 @@ instance wheelbase < MDC_C003 { } instance tire_count < MDC_C003 { - code: AAAP021 + code: ORB021 preferred_name.en: "tire count" definition.en: "number of road-contacting tires fitted as standard equipment" property_data_element_type: NON_DEPENDENT_P_DET @@ -219,7 +345,7 @@ instance tire_count < MDC_C003 { } instance road_speed_max < MDC_C003 { - code: AAAP022 + code: ORB022 preferred_name.en: "maximum road speed" definition.en: "maximum sustained speed on a paved, level road" property_data_element_type: NON_DEPENDENT_P_DET @@ -233,16 +359,46 @@ instance road_speed_max < MDC_C003 { # ----------------------------------------------------------------------------- instance Submarine < MDC_C002 { - code: AAA030 + code: ORA030 preferred_name.en: "Submarine" definition.en: "vehicle that travels fully submerged beneath the water surface" superclass: Vehicle class_type: ITEM_CLASS - applicable_properties: { max_depth_rating, submerged_endurance } + applicable_properties: { max_depth_rating, test_depth, submerged_endurance, acoustic_signature, ballast_capacity } +} + +instance test_depth < MDC_C003 { + code: ORB032 + preferred_name.en: "test depth" + definition.en: "depth at which the pressure hull was certified by trial" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Submarine + data_type: REAL_TYPE + unit: metre +} + +instance acoustic_signature < MDC_C003 { + code: ORB033 + preferred_name.en: "acoustic signature" + definition.en: "broadband radiated noise level at cruise, referenced to 1 micropascal at 1 metre" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Submarine + data_type: REAL_TYPE + unit: decibel +} + +instance ballast_capacity < MDC_C003 { + code: ORB034 + preferred_name.en: "ballast capacity" + definition.en: "total volume of the ballast tanks" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Submarine + data_type: REAL_TYPE + unit: litre } instance max_depth_rating < MDC_C003 { - code: AAAP030 + code: ORB030 preferred_name.en: "maximum depth rating" definition.en: "maximum depth at which the vehicle is certified to operate" property_data_element_type: NON_DEPENDENT_P_DET @@ -252,7 +408,7 @@ instance max_depth_rating < MDC_C003 { } instance submerged_endurance < MDC_C003 { - code: AAAP031 + code: ORB031 preferred_name.en: "submerged endurance" definition.en: "maximum time the vehicle can remain continuously submerged" property_data_element_type: NON_DEPENDENT_P_DET @@ -269,38 +425,59 @@ instance submerged_endurance < MDC_C003 { # and Submarine via the is_case_of relationship. It is simultaneously a boat, # a car, and a submarine — without triple-inheriting a superclass chain. # -# Vehicle (AAA001) -# ├── Boat (AAA010) ─┐ -# ├── Car (AAA020) ├── is_case_of -# ├── Submarine (AAA030) ─┘ -# └── TransmediumVehicle (AAA100) +# Vehicle (ORA001) +# ├── Boat (ORA010) ─┐ +# ├── Car (ORA020) ├── is_case_of +# ├── Submarine (ORA030) ─┘ +# └── TransmediumVehicle (ORA100) # ============================================================================= instance TransmediumVehicle < MDC_C002 { - code: AAA100 + code: ORA100 preferred_name.en: "Transmedium Vehicle" definition.en: "vehicle capable of operating on the water surface, on paved roads, and fully submerged" superclass: Vehicle class_type: ITEM_CLASS is_case_of: { Boat, Car, Submarine } - applicable_properties: { operating_mode, surface_water_speed, road_speed, submerged_speed } + applicable_properties: { operating_mode, surface_water_speed, road_speed, submerged_speed, transition_time, crew_size } } # --- Operating mode enumeration --- instance vehicle_mode_enum < MDC_C005 { - code: AAAE001 + code: ORE001 preferred_name.en: "vehicle operating mode" definition.en: "enumeration of the operating regimes of a transmedium vehicle" enumerated_values: { surface_water, road, underwater } } +instance hull_form_enum < MDC_C005 { + code: ORE002 + preferred_name.en: "hull form categories" + definition.en: "enumeration of the hull body arrangements" + enumerated_values: { Monohull, Catamaran, Trimaran } +} + +instance drive_layout_enum < MDC_C005 { + code: ORE003 + preferred_name.en: "drive layout categories" + definition.en: "enumeration of road drivetrain layouts" + enumerated_values: { Front_wheel_drive, Rear_wheel_drive, All_wheel_drive } +} + +instance navigation_package_enum < MDC_C005 { + code: ORE004 + preferred_name.en: "navigation package categories" + definition.en: "enumeration of navigation electronics suites" + enumerated_values: { Standard, Chartplotter, Full_avionics } +} + # --- Driving property: operating_mode --- # The value of this property determines which conditional speed property # is active on the TransmediumVehicle. instance operating_mode < MDC_C003 { - code: AAAP100 + code: ORB100 preferred_name.en: "operating mode" definition.en: "current operating regime of the transmedium vehicle" property_data_element_type: NON_DEPENDENT_P_DET @@ -314,7 +491,7 @@ instance operating_mode < MDC_C003 { # applicability. instance surface_water_speed < MDC_C003 { - code: AAAP101 + code: ORB101 preferred_name.en: "surface water speed" definition.en: "maximum sustained speed while operating on the water surface" property_data_element_type: CONDITION_DET @@ -325,7 +502,7 @@ instance surface_water_speed < MDC_C003 { } instance road_speed < MDC_C003 { - code: AAAP102 + code: ORB102 preferred_name.en: "road speed" definition.en: "maximum sustained speed while operating on a paved road" property_data_element_type: CONDITION_DET @@ -336,7 +513,7 @@ instance road_speed < MDC_C003 { } instance submerged_speed < MDC_C003 { - code: AAAP103 + code: ORB103 preferred_name.en: "submerged speed" definition.en: "maximum sustained speed while fully submerged" property_data_element_type: CONDITION_DET @@ -346,6 +523,25 @@ instance submerged_speed < MDC_C003 { condition: operating_mode == underwater } +instance transition_time < MDC_C003 { + code: ORB104 + preferred_name.en: "transition time" + definition.en: "time to change between two operating modes, e.g. road to surface water" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: TransmediumVehicle + data_type: REAL_TYPE + unit: minute +} + +instance crew_size < MDC_C003 { + code: ORB105 + preferred_name.en: "crew size" + definition.en: "number of crew required to operate the vehicle" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: TransmediumVehicle + data_type: INT_TYPE +} + # ============================================================================= # Categorical option types (power-type modelling) # ============================================================================= @@ -374,7 +570,7 @@ instance submerged_speed < MDC_C003 { # --- EngineType --- instance EngineType < MDC_C002 { - code: AAA200 + code: ORA200 preferred_name.en: "Engine Type" definition.en: "categorical class whose subclasses enumerate the propulsion options available on an OceanRunner product" superclass: Vehicle @@ -382,7 +578,7 @@ instance EngineType < MDC_C002 { } instance SingleDieselEngine < MDC_C002 { - code: AAA201 + code: ORA201 preferred_name.en: "Single Diesel Engine" definition.en: "single inline diesel powerplant driving one propeller and one axle" superclass: EngineType @@ -390,7 +586,7 @@ instance SingleDieselEngine < MDC_C002 { } instance TwinDieselEngine < MDC_C002 { - code: AAA202 + code: ORA202 preferred_name.en: "Twin Diesel Engine" definition.en: "paired diesel powerplants, one per hull sponson, driving dual propellers" superclass: EngineType @@ -398,7 +594,7 @@ instance TwinDieselEngine < MDC_C002 { } instance ElectricHybridEngine < MDC_C002 { - code: AAA203 + code: ORA203 preferred_name.en: "Electric Hybrid Engine" definition.en: "battery-electric primary propulsion with diesel range extender" superclass: EngineType @@ -408,7 +604,7 @@ instance ElectricHybridEngine < MDC_C002 { # --- InteriorPackage --- instance InteriorPackage < MDC_C002 { - code: AAA210 + code: ORA210 preferred_name.en: "Interior Package" definition.en: "categorical class whose subclasses enumerate the interior trim levels" superclass: Vehicle @@ -416,7 +612,7 @@ instance InteriorPackage < MDC_C002 { } instance StandardInterior < MDC_C002 { - code: AAA211 + code: ORA211 preferred_name.en: "Standard Interior" definition.en: "marine-grade vinyl trim with analogue instrumentation" superclass: InteriorPackage @@ -424,7 +620,7 @@ instance StandardInterior < MDC_C002 { } instance PremiumInterior < MDC_C002 { - code: AAA212 + code: ORA212 preferred_name.en: "Premium Interior" definition.en: "hand-stitched leather trim with glass cockpit display and rear bench seat" superclass: InteriorPackage @@ -434,7 +630,7 @@ instance PremiumInterior < MDC_C002 { # --- HullFinish --- instance HullFinish < MDC_C002 { - code: AAA220 + code: ORA220 preferred_name.en: "Hull Finish" definition.en: "categorical class whose subclasses enumerate the hull surface treatments" superclass: Vehicle @@ -442,7 +638,7 @@ instance HullFinish < MDC_C002 { } instance GelcoatWhite < MDC_C002 { - code: AAA221 + code: ORA221 preferred_name.en: "Gelcoat White" definition.en: "standard white gelcoat hull with antifouling bottom paint" superclass: HullFinish @@ -450,7 +646,7 @@ instance GelcoatWhite < MDC_C002 { } instance CarbonBlack < MDC_C002 { - code: AAA222 + code: ORA222 preferred_name.en: "Carbon Black" definition.en: "carbon-fibre reinforced hull with black epoxy topcoat" superclass: HullFinish @@ -461,10 +657,10 @@ instance CarbonBlack < MDC_C002 { # OceanRunner brand # ============================================================================= # -# TransmediumVehicle (AAA100) -# └── OceanRunner (BBB001) -# ├── ORCA 30 (BBB010) ← compact product line, 8 m hull -# └── ORCA 50 (BBB011) ← full-size product line, 12 m hull +# TransmediumVehicle (ORA100) +# └── OceanRunner (ORA300) +# ├── ORCA 30 (ORA310) ← compact product line, 8 m hull +# └── ORCA 50 (ORA311) ← full-size product line, 12 m hull # # The OceanRunner brand defines four configuration properties that every # product line inherits: engine_type, interior_package, hull_finish, and @@ -472,12 +668,12 @@ instance CarbonBlack < MDC_C002 { # ============================================================================= instance OceanRunner < MDC_C002 { - code: BBB001 + code: ORA300 preferred_name.en: "OceanRunner" definition.en: "brand of transmedium vehicles manufactured by OceanRunner Marine" superclass: TransmediumVehicle class_type: ITEM_CLASS - applicable_properties: { engine_type, interior_package, hull_finish, hull_identification_number } + applicable_properties: { engine_type, interior_package, hull_finish, hull_identification_number, navigation_package, warranty_period } } # --- OceanRunner configuration properties --- @@ -485,7 +681,7 @@ instance OceanRunner < MDC_C002 { # the named CATEGORICAL_CLASS. instance engine_type < MDC_C003 { - code: BBAP001 + code: ORB301 preferred_name.en: "engine type" definition.en: "propulsion configuration selected for this OceanRunner product" property_data_element_type: NON_DEPENDENT_P_DET @@ -494,7 +690,7 @@ instance engine_type < MDC_C003 { } instance interior_package < MDC_C003 { - code: BBAP002 + code: ORB302 preferred_name.en: "interior package" definition.en: "interior trim level selected for this OceanRunner product" property_data_element_type: NON_DEPENDENT_P_DET @@ -503,7 +699,7 @@ instance interior_package < MDC_C003 { } instance hull_finish < MDC_C003 { - code: BBAP003 + code: ORB303 preferred_name.en: "hull finish" definition.en: "hull surface treatment selected for this OceanRunner product" property_data_element_type: NON_DEPENDENT_P_DET @@ -512,7 +708,7 @@ instance hull_finish < MDC_C003 { } instance hull_identification_number < MDC_C003 { - code: BBAP004 + code: ORB304 preferred_name.en: "hull identification number" definition.en: "unique serial number stamped on the hull at manufacture" property_data_element_type: NON_DEPENDENT_P_DET @@ -520,12 +716,31 @@ instance hull_identification_number < MDC_C003 { data_type: STRING_TYPE } +instance navigation_package < MDC_C003 { + code: ORB305 + preferred_name.en: "navigation package" + definition.en: "navigation electronics suite fitted as standard" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: OceanRunner + data_type: ENUM_STRING_TYPE(navigation_package_enum) +} + +instance warranty_period < MDC_C003 { + code: ORB306 + preferred_name.en: "warranty period" + definition.en: "duration of the structural hull warranty" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: OceanRunner + data_type: REAL_TYPE + unit: month +} + # ============================================================================= # Product lines # ============================================================================= instance ORCA30 < MDC_C002 { - code: BBB010 + code: ORA310 preferred_name.en: "ORCA 30" short_name.en: "ORCA30" definition.en: "compact transmedium vehicle, 8 m hull, 4 occupants, road-towed" @@ -534,7 +749,7 @@ instance ORCA30 < MDC_C002 { } instance ORCA50 < MDC_C002 { - code: BBB011 + code: ORA311 preferred_name.en: "ORCA 50" short_name.en: "ORCA50" definition.en: "full-size transmedium vehicle, 12 m hull, 6 occupants, standalone trailer" @@ -560,7 +775,7 @@ instance ORCA50 < MDC_C002 { # ============================================================================= instance ORCA30_TwinDiesel_Premium_CarbonBlack < MDC_C002 { - code: BBB100 + code: ORA400 preferred_name.en: "ORCA 30 Twin Diesel Premium Carbon Black" short_name.en: "ORCA30-TD-P-CB" definition.en: "ORCA 30 configured with twin diesel engines, premium interior, and carbon black hull" @@ -583,7 +798,7 @@ instance ORCA30_TwinDiesel_Premium_CarbonBlack < MDC_C002 { # ============================================================================= instance ORCA30_UNIT_0001 < MDC_C002 { - code: BBB900001 + code: ORI0001 preferred_name.en: "ORCA 30 Hull #0001" definition.en: "individual ORCA 30 Twin Diesel Premium Carbon Black, hull number ORCA30-TH-0001" superclass: ORCA30_TwinDiesel_Premium_CarbonBlack diff --git a/reference-docs/examples/scenicspots.cddal b/reference-docs/examples/scenicspots.cddal new file mode 100644 index 0000000..de95d22 --- /dev/null +++ b/reference-docs/examples/scenicspots.cddal @@ -0,0 +1,1117 @@ +# ============================================================================= +# ScenicSpots 旅遊景點 — CDD worked example +# +# Demonstrates the full power-type modelling capability of CDD across a +# multi-country, natively multilingual domain: +# +# * Classes + plain instances + EXTENDED instances (instances used as +# classes — the capability UML cannot express) +# * Three classification dimensions as powertypes: +# ScenicSpotType (kind), ReligiousTradition (conditional), +# OfficialGrade (priority / listing level) +# * Each extended instance carries its own applicable_properties +# (e.g. HotSpring ⇒ water_temperature) — an "instance" that owns +# class-level facts +# * Configured subclasses via sub_class_selection (Temple × Buddhist ⇒ +# BuddhistTemple) — instance-of-instance used as a class +# * Registered individuals (real scenic spots) as leaf entities with +# concrete property values +# * CONDITION_DET conditional property (tradition applies only to +# temples) +# * Native multilingualism: every entity carries its native-language +# name plus English; four source languages across the dictionary +# (zh-Hant, ja, ko, it) +# +# Countries: Taiwan, Japan, Korea, Italy. +# All content authored by OpenCDD; spot facts are public knowledge. +# Individual property values are sourced from the English Wikipedia +# articles for each spot (retrieved 2026-10-07); facts only, no text. +# +# Code allocation (OpenCDD house convention): +# — stem is unique per dictionary, T is the entity type: +# A class · B property · E value list · T value term · I individual · U unit +# SP = scenicspots: SPA classes (incl. powertype members and configured +# subclasses), SPB properties, SPE enumerations, SPI registered spots. +# Blocks: 0xx country · 1xx spot types · 12x religious tradition · +# 13x official grade · 15x configured subclasses. +# ============================================================================= + +import "https://cdd.opencdd.org/dictionaries/rec20-units.cddal" + +# ----------------------------------------------------------------------------- +# Aliases (re-declare built-ins for reader clarity) +# ----------------------------------------------------------------------------- + +alias code: MDC_P001_5 +alias preferred_name: MDC_P004 +alias short_name: MDC_P005 +alias definition: MDC_P006 +alias note: MDC_P008 +alias remark: MDC_P009 +alias superclass: MDC_P010 +alias class_type: MDC_P011 +alias is_case_of: MDC_P013 +alias applicable_properties: MDC_P014 +alias property_data_element_type: MDC_P020 +alias definition_class: MDC_P021 +alias data_type: MDC_P022 +alias unit: MDC_P041 +alias condition: MDC_P028 +alias sub_class_selection: MDC_P016 +alias enumerated_values: MDC_P044 + +# ----------------------------------------------------------------------------- +# Meta-class schema +# ----------------------------------------------------------------------------- + +meta-class MDC_C002 { + code + preferred_name + short_name + definition + note + remark + synonym + superclass + class_type + is_case_of + applicable_properties + sub_class_selection + imported_properties +} + +meta-class MDC_C003 { + code + preferred_name + short_name + definition + note + remark + property_data_element_type + definition_class + data_type + unit + condition +} + +meta-class MDC_C005 { + code + preferred_name + definition + note + remark + enumerated_values +} + +# ============================================================================= +# Core categorical class: ScenicSpot +# ============================================================================= + +instance ScenicSpot < MDC_C002 { + code: SPA001 + preferred_name.en: "Scenic Spot" + preferred_name.zh-Hant: "旅遊景點" + preferred_name.ja: "観光名所" + preferred_name.ko: "관광지" + preferred_name.it: "Attrazione Turistica" + definition.en: "a place of interest that tourists visit, typically for its inherent or exhibited natural or cultural value" + superclass: UNIVERSE + class_type: ITEM_CLASS + applicable_properties: { country, spot_type, official_grade, elevation, area, established_year, admission, opening_period, annual_visitors, listing_criteria } +} + +instance annual_visitors < MDC_C003 { + code: SPB010 + preferred_name.en: "annual visitors" + preferred_name.zh-Hant: "年度遊客" + definition.en: "approximate number of visitors per year" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: REAL_TYPE +} + +instance listing_criteria < MDC_C003 { + code: SPB011 + preferred_name.en: "listing criteria" + preferred_name.zh-Hant: "遺產類別" + definition.en: "cultural, natural, or mixed character of a World Heritage listing" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: ENUM_STRING_TYPE(heritage_criteria_enum) +} + +# ============================================================================= +# Country — plain instances (data individuals, not extended instances) +# ============================================================================= + +instance Country < MDC_C002 { + code: SPA010 + preferred_name.en: "Country" + preferred_name.zh-Hant: "國家" + definition.en: "sovereign state in which a scenic spot is located" + superclass: ScenicSpot + class_type: CATEGORICAL_CLASS +} + +instance Taiwan < MDC_C002 { + code: SPA011 + preferred_name.en: "Taiwan" + preferred_name.zh-Hant: "臺灣" + definition.en: "island state in East Asia" + superclass: Country + class_type: ITEM_CLASS +} + +instance Japan < MDC_C002 { + code: SPA012 + preferred_name.en: "Japan" + preferred_name.ja: "日本" + definition.en: "island nation in East Asia" + superclass: Country + class_type: ITEM_CLASS +} + +instance Korea < MDC_C002 { + code: SPA013 + preferred_name.en: "Korea" + preferred_name.ko: "대한민국" + definition.en: "republic on the Korean Peninsula in East Asia" + superclass: Country + class_type: ITEM_CLASS +} + +instance Italy < MDC_C002 { + code: SPA014 + preferred_name.en: "Italy" + preferred_name.it: "Italia" + definition.en: "republic in Southern Europe" + superclass: Country + class_type: ITEM_CLASS +} + +# ============================================================================= +# Powertype 1 — ScenicSpotType +# +# Each member is an EXTENDED INSTANCE: it is selectable as the value of the +# spot_type CLASS_REFERENCE property AND is a class in its own right that +# carries its own applicable_properties and can be subclassed. +# +# ScenicSpotType (CATEGORICAL_CLASS) +# ├── NationalPark ⇒ trail_count +# ├── Temple ⇒ founding_dynasty, deity (+ tradition dimension) +# ├── Palace ⇒ royal_dynasty +# ├── Museum ⇒ collection_size +# ├── HotSpring ⇒ water_temperature, mineral_type +# ├── NightMarket ⇒ stall_count +# ├── Mountain ⇒ prominence +# ├── Lake ⇒ max_depth +# └── ArchaeologicalSite ⇒ excavation_status +# ============================================================================= + +instance ScenicSpotType < MDC_C002 { + code: SPA100 + preferred_name.en: "Scenic Spot Type" + preferred_name.zh-Hant: "景點類型" + definition.en: "categorical class whose subclasses enumerate the kinds of scenic spots" + superclass: ScenicSpot + class_type: CATEGORICAL_CLASS +} + +instance NationalPark < MDC_C002 { + code: SPA101 + preferred_name.en: "National Park" + preferred_name.zh-Hant: "國家公園" + preferred_name.ja: "国立公園" + preferred_name.ko: "국립공원" + definition.en: "protected area designated for conservation and public enjoyment by national law" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { trail_count, trail_length, wildlife_species } +} + +instance Temple < MDC_C002 { + code: SPA102 + preferred_name.en: "Temple" + preferred_name.zh-Hant: "寺廟" + preferred_name.ja: "寺" + preferred_name.ko: "사원" + definition.en: "building dedicated to religious worship" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { founding_dynasty, deity, treasure_count } +} + +instance Palace < MDC_C002 { + code: SPA103 + preferred_name.en: "Palace" + preferred_name.zh-Hant: "宮殿" + preferred_name.ko: "궁궐" + definition.en: "grand residence of a sovereign or aristocratic family" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { royal_dynasty, floor_area, room_count } +} + +instance Museum < MDC_C002 { + code: SPA104 + preferred_name.en: "Museum" + preferred_name.zh-Hant: "博物館" + preferred_name.ja: "博物館" + preferred_name.ko: "박물관" + preferred_name.it: "Museo" + definition.en: "institution that cares for and displays a collection of artifacts" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { collection_size, exhibition_area, special_exhibition_count } +} + +instance HotSpring < MDC_C002 { + code: SPA105 + preferred_name.en: "Hot Spring" + preferred_name.zh-Hant: "溫泉" + preferred_name.ja: "温泉" + preferred_name.ko: "온천" + definition.en: "natural spring of geothermally heated water" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { water_temperature, mineral_type, flow_rate, source_depth } +} + +instance NightMarket < MDC_C002 { + code: SPA106 + preferred_name.en: "Night Market" + preferred_name.zh-Hant: "夜市" + preferred_name.ko: "야시장" + definition.en: "street market operating primarily in the evening" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { stall_count, opening_days_per_week } +} + +instance Mountain < MDC_C002 { + code: SPA107 + preferred_name.en: "Mountain" + preferred_name.zh-Hant: "山岳" + preferred_name.ja: "山" + preferred_name.ko: "산" + definition.en: "elevated landform rising prominently above its surroundings" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { prominence, first_ascent_year, climbing_routes } +} + +instance Lake < MDC_C002 { + code: SPA108 + preferred_name.en: "Lake" + preferred_name.zh-Hant: "湖泊" + preferred_name.ja: "湖" + definition.en: "inland body of standing water" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { max_depth, surface_area, mean_depth } +} + +instance ArchaeologicalSite < MDC_C002 { + code: SPA109 + preferred_name.en: "Archaeological Site" + preferred_name.zh-Hant: "考古遺址" + preferred_name.it: "Sito Archeologico" + definition.en: "place with physical evidence of past human activity studied by archaeology" + superclass: ScenicSpotType + class_type: ITEM_CLASS + applicable_properties: { excavation_status, discovery_year } +} + +# ============================================================================= +# Powertype 2 — ReligiousTradition (conditional dimension) +# +# Applies only when spot_type == Temple; see the CONDITION_DET property +# `tradition` below. Combined with Temple via sub_class_selection this +# produces instance-of-instance classes (BuddhistTemple). +# ============================================================================= + +instance ReligiousTradition < MDC_C002 { + code: SPA120 + preferred_name.en: "Religious Tradition" + preferred_name.zh-Hant: "宗教傳統" + definition.en: "categorical class whose subclasses enumerate the religious traditions of a place of worship" + superclass: ScenicSpot + class_type: CATEGORICAL_CLASS +} + +instance Buddhist < MDC_C002 { + code: SPA121 + preferred_name.en: "Buddhist" + preferred_name.zh-Hant: "佛寺" + preferred_name.ja: "仏寺" + preferred_name.ko: "불교 사원" + definition.en: "Buddhist tradition of worship" + superclass: ReligiousTradition + class_type: ITEM_CLASS +} + +instance Taoist < MDC_C002 { + code: SPA122 + preferred_name.en: "Taoist" + preferred_name.zh-Hant: "道觀" + definition.en: "Taoist tradition of worship" + superclass: ReligiousTradition + class_type: ITEM_CLASS +} + +instance FolkShrine < MDC_C002 { + code: SPA123 + preferred_name.en: "Folk Shrine" + preferred_name.zh-Hant: "民間信仰廟宇" + definition.en: "local folk-religion tradition of worship" + superclass: ReligiousTradition + class_type: ITEM_CLASS +} + +# ============================================================================= +# Powertype 3 — OfficialGrade (priority / listing level) +# +# The user-facing "priority" of a spot: world heritage listing, national +# designation, and so on. WorldHeritage is itself an extended instance +# carrying inscription_year. +# ============================================================================= + +instance OfficialGrade < MDC_C002 { + code: SPA130 + preferred_name.en: "Official Grade" + preferred_name.zh-Hant: "官方等級" + definition.en: "categorical class whose subclasses enumerate the official listing or priority levels of a scenic spot" + superclass: ScenicSpot + class_type: CATEGORICAL_CLASS +} + +instance WorldHeritage < MDC_C002 { + code: SPA131 + preferred_name.en: "World Heritage" + preferred_name.zh-Hant: "世界遺產" + preferred_name.ja: "世界遺産" + preferred_name.ko: "세계유산" + preferred_name.it: "Patrimonio Mondiale" + definition.en: "site inscribed on the UNESCO World Heritage List" + superclass: OfficialGrade + class_type: ITEM_CLASS + applicable_properties: { inscription_year } +} + +instance NationalGrade < MDC_C002 { + code: SPA132 + preferred_name.en: "National Grade" + preferred_name.zh-Hant: "國家級" + preferred_name.ja: "国指定" + definition.en: "designation of national significance" + superclass: OfficialGrade + class_type: ITEM_CLASS +} + +instance ProvincialGrade < MDC_C002 { + code: SPA133 + preferred_name.en: "Provincial Grade" + preferred_name.zh-Hant: "省市級" + definition.en: "designation of provincial or municipal significance" + superclass: OfficialGrade + class_type: ITEM_CLASS +} + +instance LocalGrade < MDC_C002 { + code: SPA134 + preferred_name.en: "Local Grade" + preferred_name.zh-Hant: "地方級" + definition.en: "designation of local significance" + superclass: OfficialGrade + class_type: ITEM_CLASS +} + +# ============================================================================= +# Enumerations +# ============================================================================= + +instance admission_enum < MDC_C005 { + code: SPE001 + preferred_name.en: "admission categories" + definition.en: "enumeration of whether entry to the spot requires payment" + enumerated_values: { Free, Paid } +} + +instance heritage_criteria_enum < MDC_C005 { + code: SPE005 + preferred_name.en: "heritage listing criteria categories" + definition.en: "enumeration of the World Heritage listing characters" + enumerated_values: { Cultural, Natural, Mixed } +} + +instance period_enum < MDC_C005 { + code: SPE002 + preferred_name.en: "opening period categories" + definition.en: "enumeration of whether the spot operates year-round or seasonally" + enumerated_values: { YearRound, Seasonal } +} + +instance mineral_enum < MDC_C005 { + code: SPE003 + preferred_name.en: "mineral type categories" + definition.en: "enumeration of the dominant dissolved mineral character of a hot spring" + enumerated_values: { Sodium_bicarbonate, Sulfate, Carbonate, Iron } +} + +instance excavation_enum < MDC_C005 { + code: SPE004 + preferred_name.en: "excavation status categories" + definition.en: "enumeration of the state of archaeological investigation of a site" + enumerated_values: { Ongoing, Completed } +} + +# ============================================================================= +# Core properties (defined on ScenicSpot) +# ============================================================================= + +instance country < MDC_C003 { + code: SPB001 + preferred_name.en: "country" + preferred_name.zh-Hant: "所在國家" + definition.en: "sovereign state in which the scenic spot is located" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: CLASS_REFERENCE(Country) +} + +instance spot_type < MDC_C003 { + code: SPB002 + preferred_name.en: "scenic spot type" + preferred_name.zh-Hant: "景點類型" + definition.en: "kind of scenic spot, selected from the scenic spot type classification" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: CLASS_REFERENCE(ScenicSpotType) +} + +instance tradition < MDC_C003 { + code: SPB003 + preferred_name.en: "religious tradition" + preferred_name.zh-Hant: "宗教傳統" + definition.en: "religious tradition observed at the place of worship" + property_data_element_type: CONDITION_DET + definition_class: ScenicSpot + data_type: CLASS_REFERENCE(ReligiousTradition) + condition: spot_type == Temple +} + +instance official_grade < MDC_C003 { + code: SPB004 + preferred_name.en: "official grade" + preferred_name.zh-Hant: "官方等級" + definition.en: "official listing or priority level of the scenic spot" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: CLASS_REFERENCE(OfficialGrade) +} + +instance elevation < MDC_C003 { + code: SPB005 + preferred_name.en: "elevation" + preferred_name.zh-Hant: "海拔" + definition.en: "elevation above sea level of the spot's principal feature" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: REAL_TYPE + unit: metre +} + +instance area < MDC_C003 { + code: SPB006 + preferred_name.en: "area" + preferred_name.zh-Hant: "面積" + definition.en: "extent of the scenic spot" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: REAL_TYPE + unit: square_kilometre +} + +instance established_year < MDC_C003 { + code: SPB007 + preferred_name.en: "established year" + preferred_name.zh-Hant: "建立年份" + definition.en: "calendar year in which the spot was established, constructed, or first recorded" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: REAL_TYPE +} + +instance admission < MDC_C003 { + code: SPB008 + preferred_name.en: "admission" + preferred_name.zh-Hant: "門票" + definition.en: "whether entry requires payment" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: ENUM_STRING_TYPE(admission_enum) +} + +instance opening_period < MDC_C003 { + code: SPB009 + preferred_name.en: "opening period" + preferred_name.zh-Hant: "開放期間" + definition.en: "operating period of the spot" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ScenicSpot + data_type: ENUM_STRING_TYPE(period_enum) +} + +# ============================================================================= +# Type-specific properties — defined ON the extended instances +# +# These properties exist only because their definition_class is an +# instance of a CATEGORICAL_CLASS: an instance that owns class-level +# facts. This is the capability UML cannot express. +# ============================================================================= + +instance trail_count < MDC_C003 { + code: SPB101 + preferred_name.en: "trail count" + preferred_name.zh-Hant: "步道數量" + definition.en: "number of maintained hiking trails in the national park" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: NationalPark + data_type: REAL_TYPE +} + +instance founding_dynasty < MDC_C003 { + code: SPB102 + preferred_name.en: "founding dynasty" + preferred_name.zh-Hant: "創建朝代" + definition.en: "dynasty or period during which the temple was founded" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Temple + data_type: STRING_TYPE +} + +instance deity < MDC_C003 { + code: SPB103 + preferred_name.en: "principal deity" + preferred_name.zh-Hant: "主祀神明" + definition.en: "principal deity venerated at the temple" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Temple + data_type: STRING_TYPE +} + +instance royal_dynasty < MDC_C003 { + code: SPB104 + preferred_name.en: "royal dynasty" + preferred_name.zh-Hant: "皇室朝代" + definition.en: "dynasty for which the palace served as a royal residence" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Palace + data_type: STRING_TYPE +} + +instance collection_size < MDC_C003 { + code: SPB105 + preferred_name.en: "collection size" + preferred_name.zh-Hant: "館藏數量" + definition.en: "number of catalogued objects held by the museum" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Museum + data_type: REAL_TYPE +} + +instance water_temperature < MDC_C003 { + code: SPB106 + preferred_name.en: "water temperature" + preferred_name.zh-Hant: "泉水溫度" + definition.en: "temperature of the spring water at emergence" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: HotSpring + data_type: REAL_TYPE + unit: degree_celsius +} + +instance mineral_type < MDC_C003 { + code: SPB107 + preferred_name.en: "mineral type" + preferred_name.zh-Hant: "泉質" + definition.en: "dominant dissolved mineral character of the spring" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: HotSpring + data_type: ENUM_STRING_TYPE(mineral_enum) +} + +instance stall_count < MDC_C003 { + code: SPB108 + preferred_name.en: "stall count" + preferred_name.zh-Hant: "攤位數量" + definition.en: "approximate number of vending stalls at the market" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: NightMarket + data_type: REAL_TYPE +} + +instance prominence < MDC_C003 { + code: SPB109 + preferred_name.en: "topographic prominence" + preferred_name.zh-Hant: "地形突出度" + definition.en: "height of the mountain's summit relative to its surroundings" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Mountain + data_type: REAL_TYPE + unit: metre +} + +instance max_depth < MDC_C003 { + code: SPB110 + preferred_name.en: "maximum depth" + preferred_name.zh-Hant: "最大深度" + definition.en: "greatest depth of the lake" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Lake + data_type: REAL_TYPE + unit: metre +} + +instance excavation_status < MDC_C003 { + code: SPB111 + preferred_name.en: "excavation status" + preferred_name.zh-Hant: "挖掘狀態" + definition.en: "state of archaeological investigation" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ArchaeologicalSite + data_type: ENUM_STRING_TYPE(excavation_enum) +} + + +instance trail_length < MDC_C003 { + code: SPB201 + preferred_name.en: "trail length" + preferred_name.zh-Hant: "步道總長" + definition.en: "total length of maintained hiking trails" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: NationalPark + data_type: REAL_TYPE + unit: kilometre +} +instance wildlife_species < MDC_C003 { + code: SPB202 + preferred_name.en: "wildlife species" + preferred_name.zh-Hant: "野生物種數" + definition.en: "number of documented wildlife species in the protected area" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: NationalPark + data_type: INT_TYPE +} +instance treasure_count < MDC_C003 { + code: SPB203 + preferred_name.en: "treasure count" + preferred_name.zh-Hant: "指定寶物數" + definition.en: "number of holdings designated as national treasures or important cultural properties" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Temple + data_type: INT_TYPE +} +instance floor_area < MDC_C003 { + code: SPB204 + preferred_name.en: "floor area" + preferred_name.zh-Hant: "樓地板面積" + definition.en: "total floor area of the palace buildings" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Palace + data_type: REAL_TYPE + unit: square_metre +} +instance room_count < MDC_C003 { + code: SPB205 + preferred_name.en: "room count" + preferred_name.zh-Hant: "房間數" + definition.en: "number of rooms in the palace" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Palace + data_type: INT_TYPE +} +instance exhibition_area < MDC_C003 { + code: SPB206 + preferred_name.en: "exhibition area" + preferred_name.zh-Hant: "展覽面積" + definition.en: "gallery floor area open to the public" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Museum + data_type: REAL_TYPE + unit: square_metre +} +instance special_exhibition_count < MDC_C003 { + code: SPB207 + preferred_name.en: "special exhibition count" + preferred_name.zh-Hant: "特展數量" + definition.en: "number of special exhibitions held per year" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Museum + data_type: INT_TYPE +} +instance flow_rate < MDC_C003 { + code: SPB208 + preferred_name.en: "flow rate" + preferred_name.zh-Hant: "湧泉量" + definition.en: "rate of flow of the spring at emergence" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: HotSpring + data_type: REAL_TYPE + unit: litre_per_second +} +instance source_depth < MDC_C003 { + code: SPB209 + preferred_name.en: "source depth" + preferred_name.zh-Hant: "泉源深度" + definition.en: "depth below surface of the geothermal source" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: HotSpring + data_type: REAL_TYPE + unit: metre +} +instance opening_days_per_week < MDC_C003 { + code: SPB210 + preferred_name.en: "opening days per week" + preferred_name.zh-Hant: "每週營業天數" + definition.en: "number of evenings per week the market operates" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: NightMarket + data_type: INT_TYPE +} +instance first_ascent_year < MDC_C003 { + code: SPB211 + preferred_name.en: "first ascent year" + preferred_name.zh-Hant: "首登年份" + definition.en: "year of the first recorded ascent" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Mountain + data_type: REAL_TYPE +} +instance climbing_routes < MDC_C003 { + code: SPB212 + preferred_name.en: "climbing routes" + preferred_name.zh-Hant: "攀登路線數" + definition.en: "number of established climbing routes" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Mountain + data_type: INT_TYPE +} +instance surface_area < MDC_C003 { + code: SPB213 + preferred_name.en: "surface area" + preferred_name.zh-Hant: "湖面面積" + definition.en: "area of the lake surface" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Lake + data_type: REAL_TYPE + unit: square_kilometre +} +instance mean_depth < MDC_C003 { + code: SPB214 + preferred_name.en: "mean depth" + preferred_name.zh-Hant: "平均深度" + definition.en: "average depth of the lake" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: Lake + data_type: REAL_TYPE + unit: metre +} +instance discovery_year < MDC_C003 { + code: SPB215 + preferred_name.en: "discovery year" + preferred_name.zh-Hant: "發現年份" + definition.en: "year the site was discovered or first excavated" + property_data_element_type: NON_DEPENDENT_P_DET + definition_class: ArchaeologicalSite + data_type: REAL_TYPE +} +# ============================================================================= +# Grade-conditional property +# +# inscription_year applies only to spots whose official_grade selects the +# WorldHeritage extended instance — the same CONDITION_DET pattern +# OceanRunner uses for operating modes. +# ============================================================================= + +instance inscription_year < MDC_C003 { + code: SPB131 + preferred_name.en: "inscription year" + preferred_name.zh-Hant: "列入年份" + definition.en: "calendar year in which the site was inscribed on the World Heritage List" + property_data_element_type: CONDITION_DET + definition_class: ScenicSpot + data_type: REAL_TYPE + condition: official_grade == WorldHeritage +} + +# ============================================================================= +# Configured subclasses — instance-of-instance used as a class +# +# BuddhistTemple is the Temple extended instance specialized by the +# ReligiousTradition selection. It inherits ScenicSpot's properties, +# Temple's type-specific properties, and resolves the conditional +# `tradition` property. +# ============================================================================= + +instance BuddhistTemple < MDC_C002 { + code: SPA150 + preferred_name.en: "Buddhist Temple" + preferred_name.zh-Hant: "佛寺" + preferred_name.ja: "仏寺" + definition.en: "temple of the Buddhist tradition" + superclass: Temple + class_type: ITEM_CLASS + sub_class_selection: { Buddhist } +} + +instance TaoistTemple < MDC_C002 { + code: SPA151 + preferred_name.en: "Taoist Temple" + preferred_name.zh-Hant: "道觀" + definition.en: "temple of the Taoist tradition" + superclass: Temple + class_type: ITEM_CLASS + sub_class_selection: { Taoist } +} + +instance FolkTemple < MDC_C002 { + code: SPA152 + preferred_name.en: "Folk Temple" + preferred_name.zh-Hant: "民間信仰廟宇" + definition.en: "temple of the folk-religion tradition" + superclass: Temple + class_type: ITEM_CLASS + sub_class_selection: { FolkShrine } +} + +# ============================================================================= +# Registered individuals — real scenic spots (leaf entities) +# +# Each is a concrete place with native-language name, English name, and +# concrete property values, classified through the full power-type chain: +# ScenicSpot ← ScenicSpotType instance ← configured subclass. +# ============================================================================= + +# --- Taiwan --------------------------------------------------------------- + +instance LongshanTemple < MDC_C002 { + code: SPI001 + preferred_name.zh-Hant: "艋舺龍山寺" + preferred_name.en: "Longshan Temple" + short_name.en: "LongshanTemple" + definition.en: "Taiwanese folk temple in Wanhua District, Taipei, built in 1738 by settlers from Fujian during Qing rule in honour of Guanyin, with halls to folk deities such as Mazu and Guan Yu" + superclass: FolkTemple + class_type: ITEM_CLASS + country: Taiwan + spot_type: Temple + tradition: FolkShrine + official_grade: NationalGrade + established_year: 1738 + founding_dynasty: "Qing dynasty" + deity: "Guanyin (觀音)" + admission: Free + opening_period: YearRound +} + +instance TarokoNationalPark < MDC_C002 { + code: SPI002 + preferred_name.zh-Hant: "太魯閣國家公園" + preferred_name.en: "Taroko National Park" + short_name.en: "TarokoNP" + definition.en: "national park spanning Taichung, Nantou, and Hualien counties, named for the Taroko Gorge — one of only three river gorges in the world cut entirely through pure marble" + superclass: NationalPark + class_type: ITEM_CLASS + country: Taiwan + spot_type: NationalPark + official_grade: NationalGrade + elevation: 3742 + area: 920 + established_year: 1986 + trail_count: 34 + admission: Free + opening_period: YearRound +} + +instance BeitouHotSpring < MDC_C002 { + code: SPI003 + preferred_name.zh-Hant: "北投溫泉" + preferred_name.en: "Beitou Hot Spring" + short_name.en: "Beitou" + definition.en: "hot spring district in Beitou District, Taipei — named after the Ketagalan word Kipatauw — known for its geothermal valley and sulfur-free alkaline springs" + superclass: HotSpring + class_type: ITEM_CLASS + country: Taiwan + spot_type: HotSpring + official_grade: LocalGrade + water_temperature: 42 + mineral_type: Sodium_bicarbonate + established_year: 1913 + admission: Paid + opening_period: YearRound +} + +instance ShilinNightMarket < MDC_C002 { + code: SPI004 + preferred_name.zh-Hant: "士林夜市" + preferred_name.en: "Shilin Night Market" + short_name.en: "ShilinNM" + definition.en: "largest night market in Taipei" + superclass: NightMarket + class_type: ITEM_CLASS + country: Taiwan + spot_type: NightMarket + official_grade: LocalGrade + stall_count: 500 + opening_period: YearRound + admission: Free +} + +instance NationalPalaceMuseum < MDC_C002 { + code: SPI005 + preferred_name.zh-Hant: "國立故宮博物院" + preferred_name.en: "National Palace Museum" + short_name.en: "NPM" + definition.en: "national museum founded in Beijing in 1925 and re-established in Shilin, Taipei, in 1965, holding nearly 700,000 Chinese imperial artifacts spanning 8,000 years" + superclass: Museum + class_type: ITEM_CLASS + country: Taiwan + spot_type: Museum + official_grade: NationalGrade + collection_size: 697690 + established_year: 1925 + admission: Paid + opening_period: YearRound +} + +# --- Japan ---------------------------------------------------------------- + +instance Kinkakuji < MDC_C002 { + code: SPI011 + preferred_name.ja: "金閣寺" + preferred_name.en: "Kinkaku-ji" + short_name.en: "Kinkakuji" + definition.en: "Zen Buddhist temple in Kyoto, officially Rokuon-ji, whose Golden Pavilion is coated in gold leaf; the current pavilion was rebuilt in 1955 after an arson attack" + superclass: BuddhistTemple + class_type: ITEM_CLASS + country: Japan + spot_type: Temple + tradition: Buddhist + official_grade: WorldHeritage + established_year: 1397 + founding_dynasty: "Muromachi period" + inscription_year: 1994 + listing_criteria: Cultural + admission: Paid + opening_period: YearRound +} + +instance Fujisan < MDC_C002 { + code: SPI012 + preferred_name.ja: "富士山" + preferred_name.en: "Mount Fuji" + short_name.en: "Fujisan" + definition.en: "highest mountain in Japan, an active stratovolcano last erupted 1707 to 1708, and an object of pilgrimage" + superclass: Mountain + class_type: ITEM_CLASS + country: Japan + spot_type: Mountain + official_grade: WorldHeritage + elevation: 3776.24 + prominence: 3776 + inscription_year: 2013 + listing_criteria: Cultural + first_ascent_year: 663 + opening_period: Seasonal + admission: Free +} + +instance DogoOnsen < MDC_C002 { + code: SPI013 + preferred_name.ja: "道後温泉" + preferred_name.en: "Dogo Onsen" + short_name.en: "DogoOnsen" + definition.en: "hot spring in Matsuyama, Ehime, among the oldest onsen in Japan, famed for its wooden public bathhouse with the 1899 Yushinden imperial bath" + superclass: HotSpring + class_type: ITEM_CLASS + country: Japan + spot_type: HotSpring + official_grade: NationalGrade + water_temperature: 41 + mineral_type: Sodium_bicarbonate + established_year: 1894 + admission: Paid + opening_period: YearRound +} + +# --- Korea ---------------------------------------------------------------- + +instance Gyeongbokgung < MDC_C002 { + code: SPI021 + preferred_name.ko: "경복궁" + preferred_name.en: "Gyeongbokgung" + short_name.en: "Gyeongbokgung" + definition.en: "first royal palace of the Joseon dynasty in Seoul, established in 1395 and long the most visited palace in the city" + superclass: Palace + class_type: ITEM_CLASS + country: Korea + spot_type: Palace + official_grade: NationalGrade + established_year: 1395 + royal_dynasty: "Joseon dynasty" + admission: Paid + opening_period: YearRound +} + +instance MyeongdongNightMarket < MDC_C002 { + code: SPI022 + preferred_name.ko: "명동" + preferred_name.en: "Myeongdong Night Market" + short_name.en: "Myeongdong" + definition.en: "one of Seoul's principal shopping and tourism districts, ranked among the world's most expensive shopping streets" + superclass: NightMarket + class_type: ITEM_CLASS + country: Korea + spot_type: NightMarket + official_grade: LocalGrade + stall_count: 200 + opening_period: YearRound + admission: Free +} + +# --- Italy ---------------------------------------------------------------- + +instance Colosseo < MDC_C002 { + code: SPI031 + preferred_name.it: "Colosseo" + preferred_name.en: "Colosseum" + short_name.en: "Colosseum" + definition.en: "Roman amphitheatre begun under Vespasian in 72 and completed in AD 80 under Titus, the largest ancient amphitheatre ever built" + superclass: ArchaeologicalSite + class_type: ITEM_CLASS + country: Italy + spot_type: ArchaeologicalSite + official_grade: WorldHeritage + established_year: 80 + excavation_status: Completed + inscription_year: 1980 + listing_criteria: Cultural + admission: Paid + opening_period: YearRound +} + +instance UffiziGallery < MDC_C002 { + code: SPI032 + preferred_name.it: "Galleria degli Uffizi" + preferred_name.en: "Uffizi Gallery" + short_name.en: "Uffizi" + definition.en: "art museum in Florence holding the Medici collection of Italian Renaissance painting" + superclass: Museum + class_type: ITEM_CLASS + country: Italy + spot_type: Museum + official_grade: NationalGrade + established_year: 1765 + admission: Paid + opening_period: YearRound +} diff --git a/spec/antiques_spec.rb b/spec/antiques_spec.rb new file mode 100644 index 0000000..3c95c30 --- /dev/null +++ b/spec/antiques_spec.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "spec_helper" + +# The Antiques fixture demonstrates the power-type model on an +# auction-style East Asian works-of-art domain: three stacked +# classification dimensions (category × period × culture) as +# powertypes, extended instances owning kind-specific properties, +# a root-level CONDITION_DET (`reign_mark` applies only to ceramics — +# the "Mark and Period" attribution pattern), configured subclasses +# (Ceramics × Ming, Ceramics × Goryeo), and registered individuals +# (well-known museum objects) with native multilingual names. +# +# Authored by OpenCDD from standard art-historical vocabulary; the +# public department listings of international auction houses were +# consulted as reference only (notes in the private data repo). +ANTIQUES = REFERENCE_DOCS.join("examples/antiques.cddal") + +RSpec.describe "Antiques dictionary" do + let(:db) { Opencdd::Cddal.parse_file(ANTIQUES) } + + it "loads every entity" do + expect(db.entities.size).to eq(70) + end + + describe "powertypes" do + it "marks all three classification dimensions as powertypes" do + expect(db.find_by_code("ANA010").powertype?).to be(true) # Culture + expect(db.find_by_code("ANA020").powertype?).to be(true) # Period + expect(db.find_by_code("ANA100").powertype?).to be(true) # AntiqueCategory + end + + it "enumerates the object categories as extended instances" do + category = db.find_by_code("ANA100") + codes = db.instances_of(category).map(&:code).sort + expect(codes).to eq(%w[ANA101 ANA102 ANA103 ANA104 ANA105 + ANA106 ANA107 ANA108 ANA109 ANA110]) + end + + it "enumerates twelve dynasties and eras" do + period = db.find_by_code("ANA020") + codes = db.instances_of(period).map(&:code).sort + expect(codes).to eq(%w[ANA021 ANA022 ANA023 ANA024 ANA025 ANA026 + ANA027 ANA028 ANA029 ANA030 ANA031 ANA032]) + end + + it "enumerates cultures as plain instances" do + culture = db.find_by_code("ANA010") + expect(db.instances_of(culture).map(&:code).sort).to eq(%w[ANA011 ANA012 ANA013]) + end + end + + describe "extended instances as classes" do + it "gives Ceramics its own applicable properties" do + ceramics = db.find_by_code("ANA101") + expect(ceramics.properties_on_class.map(&:code).sort) + .to eq(%w[ANB101 ANB102]) # kiln, glaze + end + + it "gives Bronze its own applicable properties" do + bronze = db.find_by_code("ANA102") + expect(bronze.properties_on_class.map(&:code).sort) + .to eq(%w[ANB103 ANB104]) # vessel_form, inscription_characters + end + + it "subclasses the Ceramics extended instance via period selection" do + ming_blue_and_white = db.find_by_code("ANA150") + expect(ming_blue_and_white.parent&.code).to eq("ANA101") # Ceramics + expect(ming_blue_and_white.sub_class_selection.map(&:code)).to eq(%w[ANA027]) # Ming + + goryeo_celadon = db.find_by_code("ANA151") + expect(goryeo_celadon.parent&.code).to eq("ANA101") + expect(goryeo_celadon.sub_class_selection.map(&:code)).to eq(%w[ANA029]) # Goryeo + end + end + + describe "conditional properties" do + it "applies the reign mark only to ceramics" do + reign_mark = db.find_by_code("ANB007") + expect(reign_mark).to be_conditional + expect(reign_mark.condition.to_s).to eq("category == Ceramics") + end + end + + describe "effective properties across the power-type chain" do + it "merges core and ceramics properties for the Xuande jar" do + jar = db.find_by_code("ANI006") + codes = jar.effective_properties.map(&:code).sort + expect(codes).to include("ANB001", "ANB002", "ANB003", "ANB004", + "ANB005", "ANB006", "ANB007", + "ANB101", "ANB102") + expect(codes).not_to include("ANB113") # print_format is Ukiyo-e-only + end + + it "keeps print properties off a bronze vessel" do + ding = db.find_by_code("ANI001") + codes = ding.effective_properties.map(&:code).map(&:to_s) + expect(codes).to include("ANB103", "ANB104") # vessel_form, inscription_characters + expect(codes).not_to include("ANB101", "ANB113") + end + end + + describe "registered individuals" do + it "classifies the Maebyeong vase through the configured subclass" do + vase = db.find_by_code("ANI004") + expect(vase.parent&.code).to eq("ANA151") # GoryeoCeladon + expect(vase.effective_properties.map(&:code)).to include("ANB101", "ANB102") + end + + it "registers twelve museum objects" do + codes = db.entities.map(&:code).grep(/\AANI/) + expect(codes.sort).to eq(%w[ANI001 ANI002 ANI003 ANI004 ANI005 ANI006 + ANI007 ANI008 ANI009 ANI010 ANI011 ANI012]) + end + end + + describe "native multilingual names" do + it "carries each object's native name alongside English" do + expect(db.find_by_code("ANI001").preferred_name("zh-Hant")).to eq("毛公鼎") + expect(db.find_by_code("ANI001").preferred_name(:en)).to eq("Mao Gong Ding") + expect(db.find_by_code("ANI002").preferred_name("zh-Hant")).to eq("翠玉白菜") + expect(db.find_by_code("ANI005").preferred_name("ja")).to eq("神奈川沖浪裏") + expect(db.find_by_code("ANI004").preferred_name("ko")).to eq("고려청자 매병") + expect(db.find_by_code("ANI007").preferred_name("ko")).to eq("달항아리") + end + + it "carries native names on the classification dimensions too" do + expect(db.find_by_code("ANA101").preferred_name("zh-Hant")).to eq("陶瓷") + expect(db.find_by_code("ANA027").preferred_name("zh-Hant")).to eq("明") + expect(db.find_by_code("ANA110").preferred_name("ja")).to eq("浮世絵") + expect(db.find_by_code("ANA029").preferred_name("ko")).to eq("고려") + end + end +end diff --git a/spec/cddal_spec.rb b/spec/cddal_spec.rb index 8e87c3a..37c7da2 100644 --- a/spec/cddal_spec.rb +++ b/spec/cddal_spec.rb @@ -34,87 +34,87 @@ describe "symbolic name resolution" do it "resolves symbolic instance names in superclass" do - vehicle = database.find_by_code("AAA001") + vehicle = database.find_by_code("ORA001") expect(vehicle.preferred_name).to eq("Vehicle") - boat = database.find_by_code("AAA010") + boat = database.find_by_code("ORA010") expect(boat.parent_irdi).to eq(vehicle.irdi) - expect(boat.parent.code).to eq("AAA001") + expect(boat.parent.code).to eq("ORA001") end it "resolves symbolic instance names in applicable_properties" do - vehicle = database.find_by_code("AAA001") + vehicle = database.find_by_code("ORA001") expect(vehicle.applicable_property_irdis.map(&:code)).to contain_exactly( - "AAAP001", "AAAP002", "AAAP003", + "ORB001", "ORB002", "ORB003", ) end it "resolves is_case_of symbolic references" do - tm = database.find_by_code("AAA100") + tm = database.find_by_code("ORA100") expect(tm.is_case_of(database).map(&:code)).to contain_exactly( - "AAA010", "AAA020", "AAA030", + "ORA010", "ORA020", "ORA030", ) end it "resolves definition_class to a Klass on each Property" do - prop = database.find_by_code("AAAP010") - expect(prop.definition_class_irdi.code).to eq("AAA010") + prop = database.find_by_code("ORB010") + expect(prop.definition_class_irdi.code).to eq("ORA010") end it "resolves sub_class_selection to categorical instances" do - configured = database.find_by_code("BBB100") + configured = database.find_by_code("ORA400") expect(configured.sub_class_selection(database).map(&:code)).to contain_exactly( - "AAA202", "AAA212", "AAA222", + "ORA202", "ORA212", "ORA222", ) end end describe "class_type semantics" do it "marks categorical classes correctly" do - engine_type = database.find_by_code("AAA200") + engine_type = database.find_by_code("ORA200") expect(engine_type).to be_categorical expect(engine_type.class_type.to_s).to eq("CATEGORICAL_CLASS") end it "marks item classes correctly" do - vehicle = database.find_by_code("AAA001") + vehicle = database.find_by_code("ORA001") expect(vehicle).to be_item end end describe "data_type semantics" do it "parses CLASS_REFERENCE data types" do - prop = database.find_by_code("BBAP001") + prop = database.find_by_code("ORB301") expect(prop).to be_class_reference expect(prop.parsed_data_type.class_identifier).to eq("EngineType") end it "parses ENUM_STRING_TYPE data types" do - prop = database.find_by_code("AAAP100") + prop = database.find_by_code("ORB100") expect(prop).to be_enum expect(prop.parsed_data_type.value_list_identifier).to eq("vehicle_mode_enum") end it "parses simple REAL_TYPE data types" do - prop = database.find_by_code("AAAP001") + prop = database.find_by_code("ORB001") expect(prop.parsed_data_type.to_s).to eq("REAL_TYPE") end end describe "conditional properties" do it "marks CONDITION_DET properties as conditional" do - prop = database.find_by_code("AAAP101") + prop = database.find_by_code("ORB101") expect(prop).to be_conditional expect(prop.condition.to_s).to eq("operating_mode == surface_water") end it "marks NON_DEPENDENT_P_DET properties as non-conditional" do - prop = database.find_by_code("AAAP001") + prop = database.find_by_code("ORB001") expect(prop).not_to be_conditional end it "evaluates the condition against a binding hash" do - surface_speed = database.find_by_code("AAAP101") + surface_speed = database.find_by_code("ORB101") expect(surface_speed.active_for?(operating_mode: "surface_water")).to be(true) expect(surface_speed.active_for?(operating_mode: "road")).to be(false) end diff --git a/spec/composition_tree_spec.rb b/spec/composition_tree_spec.rb index e1fecf2..75463b6 100644 --- a/spec/composition_tree_spec.rb +++ b/spec/composition_tree_spec.rb @@ -37,15 +37,15 @@ let(:db) do Opencdd::Database.new.tap do |d| klass = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA001"), + irdi: Opencdd::IRDI.parse("ORA001"), properties: { - "MDC_P001_5" => "AAA001", + "MDC_P001_5" => "ORA001", "MDC_P011" => "ITEM_CLASS", - "MDC_P014" => "(AAAP001,AAAP002,AAAP003)", + "MDC_P014" => "(ORB001,ORB002,ORB003)", }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C002"), ) - %w[AAAP001 AAAP002 AAAP003].each_with_index do |code, i| + %w[ORB001 ORB002 ORB003].each_with_index do |code, i| d.add_entity(Opencdd::Property.new( irdi: Opencdd::IRDI.parse(code), properties: { "MDC_P001_6" => code, "MDC_P022" => "STRING_TYPE" }, @@ -59,18 +59,18 @@ end it "emits root class with three property leaves" do - tree = described_class.new(db).for(db.find_by_code("AAA001")) + tree = described_class.new(db).for(db.find_by_code("ORA001")) expect(tree.entity).to be_a(Opencdd::Klass) - expect(tree.entity.code).to eq("AAA001") + expect(tree.entity.code).to eq("ORA001") expect(tree.children.size).to eq(3) - expect(tree.children.map { |c| c.entity.code }).to contain_exactly("AAAP001", "AAAP002", "AAAP003") + expect(tree.children.map { |c| c.entity.code }).to contain_exactly("ORB001", "ORB002", "ORB003") expect(tree.children.all?(&:leaf?)).to be(true) end it "exposes only Klass entities via #classes and only Property via #properties" do - tree = described_class.new(db).for(db.find_by_code("AAA001")) - expect(tree.classes.map(&:code)).to eq(%w[AAA001]) - expect(tree.properties.map(&:code)).to contain_exactly("AAAP001", "AAAP002", "AAAP003") + tree = described_class.new(db).for(db.find_by_code("ORA001")) + expect(tree.classes.map(&:code)).to eq(%w[ORA001]) + expect(tree.properties.map(&:code)).to contain_exactly("ORB001", "ORB002", "ORB003") end end @@ -78,33 +78,33 @@ let(:db) do Opencdd::Database.new.tap do |d| target = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA010"), + irdi: Opencdd::IRDI.parse("ORA010"), properties: { - "MDC_P001_5" => "AAA010", + "MDC_P001_5" => "ORA010", "MDC_P011" => "ITEM_CLASS", - "MDC_P014" => "(AAAP101)", + "MDC_P014" => "(ORB101)", }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C002"), ) target_prop = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP101"), - properties: { "MDC_P001_6" => "AAAP101", "MDC_P022" => "STRING_TYPE" }, + irdi: Opencdd::IRDI.parse("ORB101"), + properties: { "MDC_P001_6" => "ORB101", "MDC_P022" => "STRING_TYPE" }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C003"), ) owner = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA001"), + irdi: Opencdd::IRDI.parse("ORA001"), properties: { - "MDC_P001_5" => "AAA001", + "MDC_P001_5" => "ORA001", "MDC_P011" => "ITEM_CLASS", - "MDC_P014" => "(AAAP001)", + "MDC_P014" => "(ORB001)", }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C002"), ) ref_prop = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP001"), + irdi: Opencdd::IRDI.parse("ORB001"), properties: { - "MDC_P001_6" => "AAAP001", - "MDC_P022" => "CLASS_REFERENCE(AAA010)", + "MDC_P001_6" => "ORB001", + "MDC_P022" => "CLASS_REFERENCE(ORA010)", }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C003"), ) @@ -117,13 +117,13 @@ end it "recurses into the referenced class and emits its properties" do - tree = described_class.new(db).for(db.find_by_code("AAA001")) - ref_node = tree.children.find { |c| c.entity.code == "AAAP001" } + tree = described_class.new(db).for(db.find_by_code("ORA001")) + ref_node = tree.children.find { |c| c.entity.code == "ORB001" } expect(ref_node.children.size).to eq(1) sub = ref_node.children.first expect(sub.entity).to be_a(Opencdd::Klass) - expect(sub.entity.code).to eq("AAA010") - expect(sub.children.map { |c| c.entity.code }).to eq(%w[AAAP101]) + expect(sub.entity.code).to eq("ORA010") + expect(sub.children.map { |c| c.entity.code }).to eq(%w[ORB101]) end end @@ -131,37 +131,37 @@ let(:db) do Opencdd::Database.new.tap do |d| categorical = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA200"), + irdi: Opencdd::IRDI.parse("ORA200"), properties: { - "MDC_P001_5" => "AAA200", + "MDC_P001_5" => "ORA200", "MDC_P011" => "CATEGORICAL_CLASS", }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C002"), ) option_a = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA201"), - properties: { "MDC_P001_5" => "AAA201", "MDC_P010" => "AAA200", "MDC_P011" => "ITEM_CLASS" }, + irdi: Opencdd::IRDI.parse("ORA201"), + properties: { "MDC_P001_5" => "ORA201", "MDC_P010" => "ORA200", "MDC_P011" => "ITEM_CLASS" }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C002"), ) option_b = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA202"), - properties: { "MDC_P001_5" => "AAA202", "MDC_P010" => "AAA200", "MDC_P011" => "ITEM_CLASS" }, + irdi: Opencdd::IRDI.parse("ORA202"), + properties: { "MDC_P001_5" => "ORA202", "MDC_P010" => "ORA200", "MDC_P011" => "ITEM_CLASS" }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C002"), ) owner = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA001"), + irdi: Opencdd::IRDI.parse("ORA001"), properties: { - "MDC_P001_5" => "AAA001", + "MDC_P001_5" => "ORA001", "MDC_P011" => "ITEM_CLASS", - "MDC_P014" => "(AAAP001)", + "MDC_P014" => "(ORB001)", }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C002"), ) optioned_prop = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP001"), + irdi: Opencdd::IRDI.parse("ORB001"), properties: { - "MDC_P001_6" => "AAAP001", - "MDC_P021" => "AAA200", + "MDC_P001_6" => "ORB001", + "MDC_P021" => "ORA200", "MDC_P022" => "STRING_TYPE", }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C003"), @@ -176,10 +176,10 @@ end it "recurses into every subclass of the categorical class" do - tree = described_class.new(db).for(db.find_by_code("AAA001")) + tree = described_class.new(db).for(db.find_by_code("ORA001")) prop_node = tree.children.first subclass_codes = prop_node.children.map { |c| c.entity.code } - expect(subclass_codes).to contain_exactly("AAA201", "AAA202") + expect(subclass_codes).to contain_exactly("ORA201", "ORA202") end end @@ -189,34 +189,34 @@ meta_k = Opencdd::IRDI.parse("MDC_C002") meta_p = Opencdd::IRDI.parse("MDC_C003") a = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA001"), - properties: { "MDC_P001_5" => "AAA001", "MDC_P011" => "ITEM_CLASS", "MDC_P014" => "(AAAP001)" }, + irdi: Opencdd::IRDI.parse("ORA001"), + properties: { "MDC_P001_5" => "ORA001", "MDC_P011" => "ITEM_CLASS", "MDC_P014" => "(ORB001)" }, meta_class_irdi: meta_k, ) b = Opencdd::Klass.new( irdi: Opencdd::IRDI.parse("AAA002"), - properties: { "MDC_P001_5" => "AAA002", "MDC_P011" => "ITEM_CLASS", "MDC_P014" => "(AAAP002)" }, + properties: { "MDC_P001_5" => "AAA002", "MDC_P011" => "ITEM_CLASS", "MDC_P014" => "(ORB002)" }, meta_class_irdi: meta_k, ) p_a = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP001"), - properties: { "MDC_P001_6" => "AAAP001", "MDC_P022" => "CLASS_REFERENCE(AAA002)" }, + irdi: Opencdd::IRDI.parse("ORB001"), + properties: { "MDC_P001_6" => "ORB001", "MDC_P022" => "CLASS_REFERENCE(AAA002)" }, meta_class_irdi: meta_p, ) p_b = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP002"), - properties: { "MDC_P001_6" => "AAAP002", "MDC_P022" => "CLASS_REFERENCE(AAA001)" }, + irdi: Opencdd::IRDI.parse("ORB002"), + properties: { "MDC_P001_6" => "ORB002", "MDC_P022" => "CLASS_REFERENCE(ORA001)" }, meta_class_irdi: meta_p, ) [a, b, p_a, p_b].each { |e| db.add_entity(e) } db.finalize! tree = described_class.new(db).for(a) - expect(tree.entity.code).to eq("AAA001") + expect(tree.entity.code).to eq("ORA001") class_codes = tree.classes.map(&:code) - expect(class_codes).to include("AAA001", "AAA002") + expect(class_codes).to include("ORA001", "AAA002") expect(tree.depth).to be <= 5 - path_a_count = count_on_any_path(tree, "AAA001") + path_a_count = count_on_any_path(tree, "ORA001") expect(path_a_count).to be <= 2 end @@ -225,13 +225,13 @@ meta_k = Opencdd::IRDI.parse("MDC_C002") meta_p = Opencdd::IRDI.parse("MDC_C003") a = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA001"), - properties: { "MDC_P001_5" => "AAA001", "MDC_P011" => "ITEM_CLASS", "MDC_P014" => "(AAAP001)" }, + irdi: Opencdd::IRDI.parse("ORA001"), + properties: { "MDC_P001_5" => "ORA001", "MDC_P011" => "ITEM_CLASS", "MDC_P014" => "(ORB001)" }, meta_class_irdi: meta_k, ) p = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP001"), - properties: { "MDC_P001_6" => "AAAP001", "MDC_P022" => "CLASS_REFERENCE(AAA001)" }, + irdi: Opencdd::IRDI.parse("ORB001"), + properties: { "MDC_P001_6" => "ORB001", "MDC_P022" => "CLASS_REFERENCE(ORA001)" }, meta_class_irdi: meta_p, ) db.add_entity(a) @@ -240,44 +240,44 @@ tree = described_class.new(db).for(a, max_depth: 0) expect(tree.children).to eq([]) - expect(tree.entity.code).to eq("AAA001") + expect(tree.entity.code).to eq("ORA001") end end describe "Database#composition_tree entry point" do it "delegates to a CompositionTree instance" do - vehicle = oceanrunner_db.find_by_code("AAA001") + vehicle = oceanrunner_db.find_by_code("ORA001") tree = oceanrunner_db.composition_tree(vehicle) expect(tree).to be_a(Opencdd::CompositionTree::Node) - expect(tree.entity.code).to eq("AAA001") + expect(tree.entity.code).to eq("ORA001") end end describe "OceanRunner integration" do it "emits Vehicle with three applicable properties as leaves" do - vehicle = oceanrunner_db.find_by_code("AAA001") + vehicle = oceanrunner_db.find_by_code("ORA001") tree = oceanrunner_db.composition_tree(vehicle) - expect(tree.entity.code).to eq("AAA001") + expect(tree.entity.code).to eq("ORA001") expect(tree.children.map { |c| c.entity.code }) - .to contain_exactly("AAAP001", "AAAP002", "AAAP003") + .to contain_exactly("ORB001", "ORB002", "ORB003") end it "recurses CLASS_REFERENCE into the categorical EngineType class for OceanRunner.engine_type" do - oceanrunner = oceanrunner_db.find_by_code("BBB001") + oceanrunner = oceanrunner_db.find_by_code("ORA300") tree = oceanrunner_db.composition_tree(oceanrunner) - engine_type_node = tree.children.find { |c| c.entity.code == "BBAP001" } + engine_type_node = tree.children.find { |c| c.entity.code == "ORB301" } expect(engine_type_node).not_to be_nil expect(engine_type_node.children.size).to eq(1) engine_type_class = engine_type_node.children.first expect(engine_type_class.entity).to be_a(Opencdd::Klass) - expect(engine_type_class.entity.code).to eq("AAA200") + expect(engine_type_class.entity.code).to eq("ORA200") end it "produces a non-empty tree for the configured ORCA30 product" do - orca = oceanrunner_db.find_by_code("BBB100") + orca = oceanrunner_db.find_by_code("ORA400") tree = oceanrunner_db.composition_tree(orca) - expect(tree.entity.code).to eq("BBB100") - expect(tree.properties.map(&:code)).to include("BBAP001", "BBAP002", "BBAP003", "BBAP004") + expect(tree.entity.code).to eq("ORA400") + expect(tree.properties.map(&:code)).to include("ORB301", "ORB302", "ORB303", "ORB304") end end diff --git a/spec/effective_properties_spec.rb b/spec/effective_properties_spec.rb index 9b22727..5b11e8e 100644 --- a/spec/effective_properties_spec.rb +++ b/spec/effective_properties_spec.rb @@ -7,7 +7,7 @@ describe "#for" do it "returns a Result that exposes properties and sources" do - vehicle = database.find_by_code("AAA001") + vehicle = database.find_by_code("ORA001") result = described_class.new(database).for(vehicle) expect(result).to be_a(Opencdd::EffectiveProperties::Result) expect(result.properties).to all(be_a(Opencdd::Property)) @@ -15,60 +15,60 @@ end it "is Enumerable" do - vehicle = database.find_by_code("AAA001") + vehicle = database.find_by_code("ORA001") result = described_class.new(database).for(vehicle) expect(result.size).to eq(result.properties.size) - expect(result.map(&:code)).to include("AAAP001", "AAAP002", "AAAP003") + expect(result.map(&:code)).to include("ORB001", "ORB002", "ORB003") end it "includes own applicable properties" do - vehicle = database.find_by_code("AAA001") + vehicle = database.find_by_code("ORA001") result = described_class.new(database).for(vehicle) - expect(result.codes).to contain_exactly("AAAP001", "AAAP002", "AAAP003") + expect(result.codes).to contain_exactly("ORB001", "ORB002", "ORB003") end it "includes inherited applicable properties via superclass chain" do - boat = database.find_by_code("AAA010") + boat = database.find_by_code("ORA010") result = described_class.new(database).for(boat) expect(result.codes).to contain_exactly( - "AAAP001", "AAAP002", "AAAP003", - "AAAP010", "AAAP011", "AAAP012", + "ORB001", "ORB002", "ORB003", + "ORB010", "ORB011", "ORB012", ) end it "aggregates is_case_of properties across multi-domain powertype" do - tm = database.find_by_code("AAA100") + tm = database.find_by_code("ORA100") result = described_class.new(database).for(tm) expect(result.codes).to contain_exactly( - "AAAP001", "AAAP002", "AAAP003", - "AAAP010", "AAAP011", "AAAP012", - "AAAP020", "AAAP021", "AAAP022", - "AAAP030", "AAAP031", - "AAAP100", "AAAP101", "AAAP102", "AAAP103", + "ORB001", "ORB002", "ORB003", + "ORB010", "ORB011", "ORB012", + "ORB020", "ORB021", "ORB022", + "ORB030", "ORB031", + "ORB100", "ORB101", "ORB102", "ORB103", ) expect(result.size).to eq(15) end it "cascades through OceanRunner which subclasses TransmediumVehicle" do - oceanrunner = database.find_by_code("BBB001") + oceanrunner = database.find_by_code("ORA300") result = described_class.new(database).for(oceanrunner) expect(result.size).to eq(19) - expect(result.codes).to include("BBAP001", "BBAP002", "BBAP003", "BBAP004") + expect(result.codes).to include("ORB301", "ORB302", "ORB303", "ORB304") end it "deduplicates properties reachable through multiple paths" do - tm = database.find_by_code("AAA100") + tm = database.find_by_code("ORA100") result = described_class.new(database).for(tm) irdi_counts = result.properties.map(&:irdi).tally expect(irdi_counts.values.uniq).to eq([1]) end it "tracks the source class that contributed each property" do - boat = database.find_by_code("AAA010") + boat = database.find_by_code("ORA010") result = described_class.new(database).for(boat) - hull_length = database.find_by_code("AAAP010") + hull_length = database.find_by_code("ORB010") contributing = result.sources[hull_length.irdi.to_s] - expect(contributing.map(&:code)).to include("AAA010") + expect(contributing.map(&:code)).to include("ORA010") end it "returns empty for an unknown class" do @@ -82,31 +82,31 @@ db = Opencdd::Database.new meta = Opencdd::IRDI.parse("MDC_C002") a = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("AAA001"), + irdi: Opencdd::IRDI.parse("ORA001"), properties: { "MDC_P010" => "AAA002", "MDC_P013" => "(AAA002)", - "MDC_P014" => "(AAAP001)", + "MDC_P014" => "(ORB001)", }, meta_class_irdi: meta, ) b = Opencdd::Klass.new( irdi: Opencdd::IRDI.parse("AAA002"), properties: { - "MDC_P010" => "AAA001", - "MDC_P013" => "(AAA001)", - "MDC_P014" => "(AAAP002)", + "MDC_P010" => "ORA001", + "MDC_P013" => "(ORA001)", + "MDC_P014" => "(ORB002)", }, meta_class_irdi: meta, ) p1 = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP001"), - properties: { "MDC_P001_6" => "AAAP001" }, + irdi: Opencdd::IRDI.parse("ORB001"), + properties: { "MDC_P001_6" => "ORB001" }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C003"), ) p2 = Opencdd::Property.new( - irdi: Opencdd::IRDI.parse("AAAP002"), - properties: { "MDC_P001_6" => "AAAP002" }, + irdi: Opencdd::IRDI.parse("ORB002"), + properties: { "MDC_P001_6" => "ORB002" }, meta_class_irdi: Opencdd::IRDI.parse("MDC_C003"), ) db.add_entity(a) @@ -116,15 +116,15 @@ db.finalize! result = described_class.new(db).for(a) - expect(result.codes).to contain_exactly("AAAP001", "AAAP002") + expect(result.codes).to contain_exactly("ORB001", "ORB002") end end describe "#codes_for" do it "returns the codes of the resolved properties" do - vehicle = database.find_by_code("AAA001") + vehicle = database.find_by_code("ORA001") engine = described_class.new(database) - expect(engine.codes_for(vehicle)).to contain_exactly("AAAP001", "AAAP002", "AAAP003") + expect(engine.codes_for(vehicle)).to contain_exactly("ORB001", "ORB002", "ORB003") end end end diff --git a/spec/entity_diff_spec.rb b/spec/entity_diff_spec.rb index ff37c8c..62ace5c 100644 --- a/spec/entity_diff_spec.rb +++ b/spec/entity_diff_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe Opencdd::EntityDiff do - let(:irdi) { Opencdd::IRDI.parse("0112/2///TEST#AAA001") } + let(:irdi) { Opencdd::IRDI.parse("0112/2///TEST#ORA001") } def build_entity(properties) Opencdd::Klass.new(irdi: irdi, properties: properties) @@ -115,7 +115,7 @@ def build_entity(properties) build_entity("MDC_P001" => "Y", "MDC_P002" => "new"), ) summary = diff.to_h - expect(summary[:irdi]).to eq("0112/2///TEST#AAA001") + expect(summary[:irdi]).to eq("0112/2///TEST#ORA001") expect(summary[:added]).to eq(["MDC_P002"]) expect(summary[:removed]).to eq([]) expect(summary[:changed].first).to include(field: "MDC_P001", from: "X", to: "Y") diff --git a/spec/exporters_spec.rb b/spec/exporters_spec.rb index 5e0108d..5ce5286 100644 --- a/spec/exporters_spec.rb +++ b/spec/exporters_spec.rb @@ -22,22 +22,22 @@ it "emits class_type and is_case_of for categorical classes" do text = described_class.new.to_json(database) parsed = JSON.parse(text) - engine_type = parsed.find { |n| n["code"] == "AAA200" } + engine_type = parsed.find { |n| n["code"] == "ORA200" } expect(engine_type["class_type"]).to eq("CATEGORICAL_CLASS") end it "emits value_list on properties whose data type references a value list" do text = described_class.new.to_json(database) parsed = JSON.parse(text) - operating_mode = parsed.find { |n| n["code"] == "AAAP100" } + operating_mode = parsed.find { |n| n["code"] == "ORB100" } expect(operating_mode["data_type"]).to eq("ENUM_STRING_TYPE(vehicle_mode_enum)") - expect(operating_mode["value_list"]).to eq("AAAE001") + expect(operating_mode["value_list"]).to eq("ORE001") end it "omits value_list on properties without an enumerated data type" do text = described_class.new.to_json(database) parsed = JSON.parse(text) - non_enum = parsed.find { |n| n["code"] == "AAAP001" } + non_enum = parsed.find { |n| n["code"] == "ORB001" } expect(non_enum["data_type"]).to eq("REAL_TYPE") expect(non_enum).not_to have_key("value_list") end @@ -85,9 +85,9 @@ it "emits the dates hash when an entity carries MDC_P003_* values" do meta = Opencdd::IRDI.parse("0112/2///62656_1#MDC_C002") klass = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("0112/2///62656_4#AAA001"), + irdi: Opencdd::IRDI.parse("0112/2///62656_4#ORA001"), properties: { - Opencdd::PropertyIds::MDC_P001_5 => "0112/2///62656_4#AAA001", + Opencdd::PropertyIds::MDC_P001_5 => "0112/2///62656_4#ORA001", Opencdd::PropertyIds::MDC_P003_1 => "2024-01-01", Opencdd::PropertyIds::MDC_P003_2 => "2024-06-01", Opencdd::PropertyIds::MDC_P066 => "guid-abc-123", @@ -186,19 +186,19 @@ it "emits inheritance edges for the superclass hierarchy" do diagram = described_class.new.to_diagram(database) - expect(diagram).to include("AAA001 <|-- AAA010") - expect(diagram).to include("AAA001 <|-- AAA020") - expect(diagram).to include("AAA001 <|-- AAA030") + expect(diagram).to include("ORA001 <|-- ORA010") + expect(diagram).to include("ORA001 <|-- ORA020") + expect(diagram).to include("ORA001 <|-- ORA030") end it "emits is_case_of edges for powertype composition" do diagram = described_class.new.to_diagram(database) - expect(diagram).to include("<.. AAA100 : is_case_of") + expect(diagram).to include("<.. ORA100 : is_case_of") end it "includes class_type annotation for categorical classes" do diagram = described_class.new.to_diagram(database) - expect(diagram).to match(/class AAA200 \{\s*<>/) + expect(diagram).to match(/class ORA200 \{\s*<>/) end end end diff --git a/spec/json_exporter_spec.rb b/spec/json_exporter_spec.rb new file mode 100644 index 0000000..3c5a0a5 --- /dev/null +++ b/spec/json_exporter_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "spec_helper" +require "json" + +# Regression: property payloads once carried data_type twice — a string +# key from the field registry and a symbol key from the property node +# wrapper — which JSON generation rejects as a duplicate key. The +# ScenicSpots fixture is the first CDDAL dictionary whose properties +# carry MDC_P022, so it exercises the collision. +RSpec.describe Opencdd::Exporters::Json do + let(:db) { Opencdd::Cddal.parse_file(REFERENCE_DOCS.join("examples/scenicspots.cddal")) } + + it "round-trips a database whose properties declare data types" do + json = described_class.new.to_json(db) + expect { JSON.parse(json) }.not_to raise_error + end + + it "emits exactly one data_type per property, from the parsed type" do + json = described_class.new.to_json(db) + tradition = JSON.parse(json).find { |e| e["code"] == "SPB003" } + expect(tradition["data_type"]).to eq("CLASS_REFERENCE(ReligiousTradition)") + end +end diff --git a/spec/model/entity_store_spec.rb b/spec/model/entity_store_spec.rb index 14b079d..9aac29e 100644 --- a/spec/model/entity_store_spec.rb +++ b/spec/model/entity_store_spec.rb @@ -46,7 +46,7 @@ Dir.mktmpdir("entity-store") do |dir| oceanrunner.save_to_directory(dir) db2 = Opencdd::Database.load_from_directory(dir) - vehicle = db2.find_by_code("AAA001") + vehicle = db2.find_by_code("ORA001") expect(vehicle).not_to be_nil expect(vehicle.preferred_name).to eq("Vehicle") end @@ -56,10 +56,10 @@ Dir.mktmpdir("entity-store") do |dir| oceanrunner.save_to_directory(dir) db2 = Opencdd::Database.load_from_directory(dir) - engine = db2.find_by_code("AAA200") + engine = db2.find_by_code("ORA200") expect(engine).to be_powertype expect(db2.instances_of(engine).map(&:code).sort) - .to eq(%w[AAA201 AAA202 AAA203]) + .to eq(%w[ORA201 ORA202 ORA203]) end end @@ -67,7 +67,7 @@ Dir.mktmpdir("entity-store") do |dir| oceanrunner.save_to_directory(dir) yaml_files = Dir.glob("#{dir}/**/*.yaml") - expect(yaml_files.size).to eq(40) + expect(yaml_files.size).to eq(62) end end end @@ -82,7 +82,7 @@ Dir.mktmpdir("entity-store") do |dir| store = described_class.new(dir) store.save_database(oceanrunner) - expect(Dir.glob("#{dir}/**/*.yaml").size).to eq(40) + expect(Dir.glob("#{dir}/**/*.yaml").size).to eq(62) end end @@ -91,7 +91,7 @@ store = described_class.new(dir) store.save_database(oceanrunner) db = store.load_database - expect(db.entities.size).to eq(40) + expect(db.entities.size).to eq(62) end end end diff --git a/spec/model/yaml_model_spec.rb b/spec/model/yaml_model_spec.rb index 5d6e6c4..e66ac31 100644 --- a/spec/model/yaml_model_spec.rb +++ b/spec/model/yaml_model_spec.rb @@ -40,7 +40,7 @@ it "round-trips preserving preferred_name (multilingual)" do yaml = oceanrunner.to_yaml db2 = Opencdd::Database.from_yaml(yaml) - vehicle = db2.find_by_code("AAA001") + vehicle = db2.find_by_code("ORA001") expect(vehicle).not_to be_nil expect(vehicle.preferred_name).to eq("Vehicle") end @@ -48,7 +48,7 @@ it "round-trips preserving class_type" do yaml = oceanrunner.to_yaml db2 = Opencdd::Database.from_yaml(yaml) - engine = db2.find_by_code("AAA200") + engine = db2.find_by_code("ORA200") expect(engine).not_to be_nil expect(engine.class_type.to_s).to eq("CATEGORICAL_CLASS") end @@ -56,16 +56,16 @@ it "round-trips preserving powertype semantics" do yaml = oceanrunner.to_yaml db2 = Opencdd::Database.from_yaml(yaml) - engine = db2.find_by_code("AAA200") + engine = db2.find_by_code("ORA200") expect(engine).to be_powertype expect(db2.instances_of(engine).map(&:code).sort) - .to eq(%w[AAA201 AAA202 AAA203]) + .to eq(%w[ORA201 ORA202 ORA203]) end it "round-trips preserving superclass relationships" do yaml = oceanrunner.to_yaml db2 = Opencdd::Database.from_yaml(yaml) - boat = db2.find_by_code("AAA010") + boat = db2.find_by_code("ORA010") expect(boat).not_to be_nil expect(boat.parent_irdi).not_to be_nil end @@ -73,7 +73,7 @@ describe Opencdd::Entity::Yaml do it "serializes a single entity to YAML with semantic names" do - vehicle = oceanrunner.find_by_code("AAA001") + vehicle = oceanrunner.find_by_code("ORA001") yaml_entity = Opencdd::Entity::Yaml.from_entity(vehicle) yaml = yaml_entity.to_yaml expect(yaml).to include("preferred_name:") @@ -81,12 +81,12 @@ end it "deserializes from YAML back to an Entity" do - vehicle = oceanrunner.find_by_code("AAA001") + vehicle = oceanrunner.find_by_code("ORA001") yaml_entity = Opencdd::Entity::Yaml.from_entity(vehicle) yaml = yaml_entity.to_yaml parsed = Opencdd::Entity::Yaml.from_yaml(yaml) entity = parsed.to_entity - expect(entity.code).to eq("AAA001") + expect(entity.code).to eq("ORA001") expect(entity.preferred_name).to eq("Vehicle") end end @@ -102,7 +102,7 @@ describe "collection fields as YAML arrays" do it "emits applicable_properties as an array" do - vehicle = oceanrunner.find_by_code("AAA001") + vehicle = oceanrunner.find_by_code("ORA001") yaml_entity = Opencdd::Entity::Yaml.from_entity(vehicle) yaml = yaml_entity.to_yaml expect(yaml).to include("applicable_properties:") diff --git a/spec/parcel/round_trip_matrix_spec.rb b/spec/parcel/round_trip_matrix_spec.rb index 978e8cb..abdb632 100644 --- a/spec/parcel/round_trip_matrix_spec.rb +++ b/spec/parcel/round_trip_matrix_spec.rb @@ -32,18 +32,18 @@ meta-class MDC_C011 { code preferred_name relation_type } instance Vehicle < MDC_C002 { - code: AAA001 + code: ORA001 preferred_name.en: "Vehicle" class_type: ITEM_CLASS } instance vehicle_length < MDC_C003 { - code: AAAP001 + code: ORB001 preferred_name.en: "vehicle length" definition_class: Vehicle data_type: REAL_TYPE } instance mode_enum < MDC_C005 { - code: AAAE001 + code: ORE001 preferred_name.en: "operating mode" enumerated_values: { ROAD, WATER } } @@ -129,7 +129,7 @@ def write_then_read(db, parcel_id:) it "preserves property codes on Property entities (the P24 regression)" do reread = write_then_read(synthetic_per_type, parcel_id: "SYNTH") - prop = reread.properties.find { |p| p.code == "AAAP001" } + prop = reread.properties.find { |p| p.code == "ORB001" } expect(prop).not_to be_nil expect(prop.preferred_name).to eq("vehicle length") end @@ -152,9 +152,9 @@ def write_then_read(db, parcel_id:) it "round-trips a synthetic Class-only workbook through CDDAL" do db = Opencdd::Database.new klass = Opencdd::Klass.new( - irdi: Opencdd::IRDI.parse("0112/2///61360_4#AAA001"), + irdi: Opencdd::IRDI.parse("0112/2///OCEANRUNNER#ORA001"), properties: { - "MDC_P001_5" => "0112/2///61360_4#AAA001", + "MDC_P001_5" => "0112/2///OCEANRUNNER#ORA001", "MDC_P004.en" => "Round-trip class", "MDC_P011" => "ITEM_CLASS", }, @@ -165,7 +165,7 @@ def write_then_read(db, parcel_id:) reread = write_then_read(db, parcel_id: "ROUNDTRIP") expect(reread.classes.size).to eq(1) - expect(reread.classes.first.code).to eq("AAA001") + expect(reread.classes.first.code).to eq("ORA001") end end end diff --git a/spec/powertype_spec.rb b/spec/powertype_spec.rb index 943a2b6..40aaf44 100644 --- a/spec/powertype_spec.rb +++ b/spec/powertype_spec.rb @@ -18,13 +18,13 @@ RSpec.describe "Powertype API (4-layer ontology)", :cddal do let(:db) { Opencdd::Cddal.parse_file(REFERENCE_DOCS.join("examples/oceanrunner.cddal")) } - let(:engine_type) { db.find_by_code("AAA200") } - let(:interior_package) { db.find_by_code("AAA210") } - let(:hull_finish) { db.find_by_code("AAA220") } - let(:single_diesel) { db.find_by_code("AAA201") } - let(:twin_diesel) { db.find_by_code("AAA202") } - let(:electric_hybrid) { db.find_by_code("AAA203") } - let(:vehicle) { db.find_by_code("AAA001") } + let(:engine_type) { db.find_by_code("ORA200") } + let(:interior_package) { db.find_by_code("ORA210") } + let(:hull_finish) { db.find_by_code("ORA220") } + let(:single_diesel) { db.find_by_code("ORA201") } + let(:twin_diesel) { db.find_by_code("ORA202") } + let(:electric_hybrid) { db.find_by_code("ORA203") } + let(:vehicle) { db.find_by_code("ORA001") } describe "Klass#powertype?" do it "returns true for CATEGORICAL_CLASS" do @@ -42,7 +42,7 @@ describe "Klass#categorical_instances" do it "returns the powertype instances of a categorical class" do codes = engine_type.categorical_instances(db).map(&:code) - expect(codes).to contain_exactly("AAA201", "AAA202", "AAA203") + expect(codes).to contain_exactly("ORA201", "ORA202", "ORA203") end it "is empty for a non-powertype class" do @@ -55,12 +55,12 @@ it "returns InteriorPackage's options" do codes = interior_package.categorical_instances(db).map(&:code) - expect(codes).to contain_exactly("AAA211", "AAA212") + expect(codes).to contain_exactly("ORA211", "ORA212") end it "returns HullFinish's options" do codes = hull_finish.categorical_instances(db).map(&:code) - expect(codes).to contain_exactly("AAA221", "AAA222") + expect(codes).to contain_exactly("ORA221", "ORA222") end end @@ -74,7 +74,7 @@ # OceanRunner inherits from TransmediumVehicle. ORCA30 inherits # from OceanRunner. ORCA30's powertype_owners should include # any categorical classes in the chain. - orca30 = db.find_by_code("BBB010") + orca30 = db.find_by_code("ORA310") expect(orca30.powertype_owners(db)).to be_an(Array) end end @@ -82,24 +82,24 @@ describe "Database#categorical_classes" do it "returns all categorical classes in the database" do codes = db.categorical_classes.map(&:code) - expect(codes).to contain_exactly("AAA200", "AAA210", "AAA220") + expect(codes).to contain_exactly("ORA200", "ORA210", "ORA220") end end describe "Database#instances_of" do it "accepts a Klass" do codes = db.instances_of(engine_type).map(&:code) - expect(codes).to contain_exactly("AAA201", "AAA202", "AAA203") + expect(codes).to contain_exactly("ORA201", "ORA202", "ORA203") end it "accepts an IRDI" do codes = db.instances_of(engine_type.irdi).map(&:code) - expect(codes).to contain_exactly("AAA201", "AAA202", "AAA203") + expect(codes).to contain_exactly("ORA201", "ORA202", "ORA203") end it "accepts a code String" do - codes = db.instances_of("AAA200").map(&:code) - expect(codes).to contain_exactly("AAA201", "AAA202", "AAA203") + codes = db.instances_of("ORA200").map(&:code) + expect(codes).to contain_exactly("ORA201", "ORA202", "ORA203") end it "returns [] for a non-powertype class" do @@ -127,10 +127,10 @@ it "preserves powertype semantics through serialize → parse" do out = Opencdd::Cddal.serialize(db) db2 = Opencdd::Cddal.parse(out) - engine2 = db2.find_by_code("AAA200") + engine2 = db2.find_by_code("ORA200") expect(engine2).to be_powertype expect(engine2.categorical_instances(db2).map(&:code).sort) - .to eq(%w[AAA201 AAA202 AAA203]) + .to eq(%w[ORA201 ORA202 ORA203]) end end end diff --git a/spec/scenicspots_spec.rb b/spec/scenicspots_spec.rb new file mode 100644 index 0000000..fb2c533 --- /dev/null +++ b/spec/scenicspots_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "spec_helper" + +# The ScenicSpots fixture demonstrates the full power-type chain: +# classes, plain instances (countries), extended instances +# (ScenicSpotType members carrying own applicable properties), +# configured subclasses (BuddhistTemple), and registered individuals +# (real scenic spots) with native multilingual names. +SCENICSPOTS = REFERENCE_DOCS.join("examples/scenicspots.cddal") + +RSpec.describe "ScenicSpots dictionary" do + let(:db) { Opencdd::Cddal.parse_file(SCENICSPOTS) } + + it "loads every entity" do + expect(db.entities.size).to eq(83) + end + + describe "powertypes" do + it "marks all three classification dimensions as powertypes" do + expect(db.find_by_code("SPA100").powertype?).to be(true) # ScenicSpotType + expect(db.find_by_code("SPA120").powertype?).to be(true) # ReligiousTradition + expect(db.find_by_code("SPA130").powertype?).to be(true) # OfficialGrade + end + + it "enumerates the spot kinds as extended instances" do + spot_type = db.find_by_code("SPA100") + codes = db.instances_of(spot_type).map(&:code).sort + expect(codes).to eq(%w[SPA101 SPA102 SPA103 SPA104 SPA105 SPA106 SPA107 SPA108 SPA109]) + end + + it "enumerates countries as plain instances" do + country = db.find_by_code("SPA010") + expect(db.instances_of(country).map(&:code).sort).to eq(%w[SPA011 SPA012 SPA013 SPA014]) + end + end + + describe "extended instances as classes" do + it "gives HotSpring its own applicable properties" do + hot_spring = db.find_by_code("SPA105") + expect(hot_spring.properties_on_class.map(&:code).sort) + .to eq(%w[SPB106 SPB107 SPB208 SPB209]) # water_temperature, mineral_type, flow_rate, source_depth + end + + it "gives Temple its own applicable properties" do + temple = db.find_by_code("SPA102") + expect(temple.properties_on_class.map(&:code).sort).to eq(%w[SPB102 SPB103 SPB203]) + end + + it "classifies Longshan Temple through the folk-tradition configured subclass" do + folk_temple = db.find_by_code("SPA152") + expect(folk_temple.parent&.code).to eq("SPA102") # Temple + expect(folk_temple.sub_class_selection.map(&:code)).to eq(%w[SPA123]) # FolkShrine + expect(db.find_by_code("SPI001").parent&.code).to eq("SPA152") # Longshan Temple + end + + it "subclasses the Temple extended instance via a configured subclass" do + buddhist_temple = db.find_by_code("SPA150") + expect(buddhist_temple.parent&.code).to eq("SPA102") # Temple + expect(buddhist_temple.sub_class_selection.map(&:code)).to eq(%w[SPA121]) # Buddhist + end + end + + describe "conditional properties" do + it "applies the tradition property only to temples" do + tradition = db.find_by_code("SPB003") + expect(tradition).to be_conditional + expect(tradition.condition.to_s).to eq("spot_type == Temple") + end + + it "applies inscription year only to world-heritage spots" do + inscription = db.find_by_code("SPB131") + expect(inscription).to be_conditional + expect(inscription.condition.to_s).to eq("official_grade == WorldHeritage") + end + end + + describe "effective properties across the power-type chain" do + it "merges core, temple-specific, and tradition properties for Kinkaku-ji" do + kinkakuji = db.find_by_code("SPI011") + codes = kinkakuji.effective_properties.map(&:code).sort + expect(codes).to include("SPB001", "SPB002", "SPB003", "SPB004", + "SPB007", "SPB008", "SPB009", + "SPB102", "SPB103") + expect(codes).not_to include("SPB106") # water_temperature is HotSpring-only + end + + it "keeps hot-spring properties off a national park" do + taroko = db.find_by_code("SPI002") + codes = taroko.effective_properties.map(&:code).map(&:to_s) + expect(codes).to include("SPB101") # trail_count + expect(codes).not_to include("SPB106", "SPB102") + end + end + + describe "native multilingual names" do + it "carries each country's native name alongside English" do + expect(db.find_by_code("SPI001").preferred_name("zh-Hant")).to eq("艋舺龍山寺") + expect(db.find_by_code("SPI001").preferred_name(:en)).to eq("Longshan Temple") + expect(db.find_by_code("SPI011").preferred_name(:ja)).to eq("金閣寺") + expect(db.find_by_code("SPI011").preferred_name(:en)).to eq("Kinkaku-ji") + expect(db.find_by_code("SPI021").preferred_name(:ko)).to eq("경복궁") + expect(db.find_by_code("SPI031").preferred_name(:it)).to eq("Colosseo") + end + end +end diff --git a/spec/validator/class_reference_rule_spec.rb b/spec/validator/class_reference_rule_spec.rb index 58e0751..946a510 100644 --- a/spec/validator/class_reference_rule_spec.rb +++ b/spec/validator/class_reference_rule_spec.rb @@ -25,24 +25,24 @@ } instance EngineType < MDC_C002 { - code: AAA200 + code: ORA200 preferred_name.en: "Engine Type" class_type: CATEGORICAL_CLASS } instance SingleDiesel < MDC_C002 { - code: AAA201 + code: ORA201 preferred_name.en: "Single Diesel" superclass: EngineType class_type: ITEM_CLASS } instance TwinDiesel < MDC_C002 { - code: AAA202 + code: ORA202 preferred_name.en: "Twin Diesel" superclass: EngineType class_type: ITEM_CLASS } instance Vehicle < MDC_C002 { - code: AAA001 + code: ORA001 preferred_name.en: "Vehicle" class_type: ITEM_CLASS } @@ -57,10 +57,10 @@ let(:db) { Opencdd::Cddal.parse(cddal) } - let(:engine_type) { db.find_by_code("AAA200") } - let(:single_diesel) { db.find_by_code("AAA201") } - let(:twin_diesel) { db.find_by_code("AAA202") } - let(:vehicle) { db.find_by_code("AAA001") } + let(:engine_type) { db.find_by_code("ORA200") } + let(:single_diesel) { db.find_by_code("ORA201") } + let(:twin_diesel) { db.find_by_code("ORA202") } + let(:vehicle) { db.find_by_code("ORA001") } let(:engine_prop) { db.find_by_code("AAAP200") } def context_for(property, data_type_override = nil) diff --git a/spec/visitor_spec.rb b/spec/visitor_spec.rb index 74628b9..12ad553 100644 --- a/spec/visitor_spec.rb +++ b/spec/visitor_spec.rb @@ -77,7 +77,7 @@ def visit_class(klass) end.new collector.visit_classes(database) - expect(collector.visited).to include("AAA001", "AAA010", "AAA100", "BBB001") + expect(collector.visited).to include("ORA001", "ORA010", "ORA100", "ORA300") end end end