Skip to content

Commit 25bef97

Browse files
authored
Merge pull request #36 from pvlib/additional_validations
Additional validations
2 parents dac5c41 + ef7d405 commit 25bef97

8 files changed

+799
-47
lines changed

.github/workflows/validate_xml_with_xsd.py

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import sys
2+
import argparse
3+
from lxml import etree, isoschematron
4+
5+
6+
def validate_xsd(xml_doc, xsd_filepath):
7+
"""Validate XML document against XSD schema."""
8+
with open(xsd_filepath, 'rb') as xsd:
9+
xsd_doc = etree.parse(xsd)
10+
schema = etree.XMLSchema(xsd_doc)
11+
xsd_valid = schema.validate(xml_doc)
12+
13+
if not xsd_valid:
14+
print('! XSD validation failed.')
15+
for error in schema.error_log:
16+
print(' -', error.message)
17+
return False
18+
else:
19+
print('XSD validation passed.')
20+
return True
21+
22+
23+
def validate_schematron(xml_doc, sch_filepath, validation_type):
24+
"""Validate XML document against Schematron schema."""
25+
with open(sch_filepath, 'rb') as sch:
26+
sch_doc = etree.parse(sch)
27+
schematron = isoschematron.Schematron(sch_doc, store_report=True)
28+
sch_valid = schematron.validate(xml_doc)
29+
30+
if not sch_valid:
31+
print(f'! Schematron {validation_type} validation failed.')
32+
svrl = schematron.validation_report
33+
if svrl is not None:
34+
for failed in svrl.xpath('//svrl:failed-assert',
35+
namespaces={'svrl': 'http://purl.oclc.org/dsdl/svrl'}):
36+
location = failed.get('location', 'unknown')
37+
messages = failed.xpath('svrl:text/text()',
38+
namespaces={'svrl': 'http://purl.oclc.org/dsdl/svrl'})
39+
message = messages[0].strip() if messages else 'No message provided'
40+
print(f' - {location}: {message}')
41+
return False
42+
else:
43+
print(f'Schematron {validation_type} validation passed.')
44+
return True
45+
46+
47+
if __name__ == '__main__':
48+
parser = argparse.ArgumentParser(description='Validate XML against XSD and Schematron schemas')
49+
parser.add_argument('xml_file', help='Path to XML file to validate')
50+
parser.add_argument('--xsd', help='Path to XSD schema file')
51+
parser.add_argument('--schematron', help='Path to Schematron schema file')
52+
53+
args = parser.parse_args()
54+
55+
if not args.xsd and not args.schematron:
56+
print('Error: Either --xsd or --schematron must be specified')
57+
sys.exit(1)
58+
59+
# Parse XML document once
60+
with open(args.xml_file, 'rb') as xml:
61+
xml_doc = etree.parse(xml)
62+
63+
validation_passed = True
64+
65+
# Validate against XSD if specified
66+
if args.xsd:
67+
validation_passed = validate_xsd(xml_doc, args.xsd)
68+
69+
# Validate against Schematron if specified
70+
if args.schematron:
71+
# Determine validation type from filename
72+
validation_type = "unknown"
73+
if "structure" in args.schematron.lower():
74+
validation_type = "structure"
75+
elif "references" in args.schematron.lower():
76+
validation_type = "references"
77+
elif "business" in args.schematron.lower():
78+
validation_type = "business"
79+
80+
schematron_result = validate_schematron(xml_doc, args.schematron, validation_type)
81+
validation_passed = validation_passed and schematron_result
82+
83+
# Exit with error if validation failed
84+
if not validation_passed:
85+
sys.exit(1)

.github/workflows/validate_xml_with_xsd.yml renamed to .github/workflows/validate_xml_with_xsd_and_schematron.yml

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
# .github/workflows/validate_xml_with_xsd.yml
1+
# .github/workflows/validate_xml_with_xsd_and_schematron.yml
22
name: Validate PVCollada XML examples
33

44
on:
55
push:
66
paths:
77
- Examples/**
88
- schema/**
9-
- .github/workflows/validate_xml_with_xsd.py
10-
- .github/workflows/validate_xml_with_xsd.yml
9+
- .github/workflows/validate_xml_with_xsd_and_schematron.py
10+
- .github/workflows/validate_xml_with_xsd_and_schematron.yml
1111

1212
pull_request:
1313
paths:
1414
- Examples/**
1515
- schema/**
16-
- .github/workflows/validate_xml_with_xsd.py
17-
- .github/workflows/validate_xml_with_xsd.yml
16+
- .github/workflows/validate_xml_with_xsd_and_schematron.py
17+
- .github/workflows/validate_xml_with_xsd_and_schematron.yml
1818

1919
workflow_dispatch:
2020

@@ -60,19 +60,43 @@ jobs:
6060
- name: Install lxml Python dependency
6161
run: pip3 install lxml==5.3.1
6262

63-
- name: Checkout PVCollada and COLLADA schemas, the validation script, and the XML doc
63+
- name: Checkout schemas, validation script, and XML doc
6464
uses: actions/checkout@v4
6565
with:
6666
sparse-checkout: |
6767
schema/pvcollada_schema_0.1.xsd
68+
schema/pvcollada_structure_2.0.sch
69+
schema/pvcollada_references_2.0.sch
70+
schema/pvcollada_business_2.0.sch
6871
schema/collada_schema_1_5.xsd
69-
.github/workflows/validate_xml_with_xsd.py
72+
.github/workflows/validate_xml_with_xsd_and_schematron.py
7073
${{ matrix.xml_doc }}
7174
sparse-checkout-cone-mode: false
7275

73-
- name: Run validation
76+
- name: Validate against XSD Schema
7477
working-directory: schema
7578
run: |
76-
python3 "$GITHUB_WORKSPACE"/.github/workflows/validate_xml_with_xsd.py \
77-
pvcollada_schema_0.1.xsd \
78-
"$GITHUB_WORKSPACE"/"${{ matrix.xml_doc }}"
79+
python3 "$GITHUB_WORKSPACE"/.github/workflows/validate_xml_with_xsd_and_schematron.py \
80+
"$GITHUB_WORKSPACE"/"${{ matrix.xml_doc }}" \
81+
--xsd pvcollada_schema_0.1.xsd
82+
83+
- name: Validate against Structure Schematron
84+
working-directory: schema
85+
run: |
86+
python3 "$GITHUB_WORKSPACE"/.github/workflows/validate_xml_with_xsd_and_schematron.py \
87+
"$GITHUB_WORKSPACE"/"${{ matrix.xml_doc }}" \
88+
--schematron pvcollada_structure_2.0.sch
89+
90+
- name: Validate against References Schematron
91+
working-directory: schema
92+
run: |
93+
python3 "$GITHUB_WORKSPACE"/.github/workflows/validate_xml_with_xsd_and_schematron.py \
94+
"$GITHUB_WORKSPACE"/"${{ matrix.xml_doc }}" \
95+
--schematron pvcollada_references_2.0.sch
96+
97+
- name: Validate against Business Rules Schematron
98+
working-directory: schema
99+
run: |
100+
python3 "$GITHUB_WORKSPACE"/.github/workflows/validate_xml_with_xsd_and_schematron.py \
101+
"$GITHUB_WORKSPACE"/"${{ matrix.xml_doc }}" \
102+
--schematron pvcollada_business_2.0.sch

schema/pvcollada_business_2.0.sch

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<schema xmlns="http://purl.oclc.org/dsdl/schematron">
3+
4+
<title>PVCollada Business Rules - Conditional requirements and domain logic</title>
5+
6+
<ns prefix="pv" uri="http://www.example.com/pvcollada"/>
7+
<ns prefix="collada" uri="http://www.collada.org/2008/03/COLLADASchema"/>
8+
9+
<!-- Pattern: Rack field requirements based on rack_type -->
10+
<pattern id="rack_fixed_tilt_requirements">
11+
<rule context="pv:rack[pv:rack_type='fixed_tilt']">
12+
<!-- Required fields for fixed_tilt -->
13+
<assert test="pv:azimuth">
14+
Fixed tilt rack must have azimuth element
15+
</assert>
16+
<assert test="pv:tilt">
17+
Fixed tilt rack must have tilt element
18+
</assert>
19+
20+
<!-- Forbidden fields for fixed_tilt -->
21+
<assert test="not(pv:tracker_azimuth)">
22+
Fixed tilt rack must not have tracker_azimuth element
23+
</assert>
24+
<assert test="not(pv:tracker_slope)">
25+
Fixed tilt rack must not have tracker_slope element
26+
</assert>
27+
<assert test="not(pv:tracker_height)">
28+
Fixed tilt rack must not have tracker_height element
29+
</assert>
30+
</rule>
31+
</pattern>
32+
33+
<pattern id="rack_tracker_requirements">
34+
<rule context="pv:rack[pv:rack_type='tracker']">
35+
<!-- Required fields for tracker -->
36+
<assert test="pv:tracker_azimuth">
37+
Tracker rack must have tracker_azimuth element
38+
</assert>
39+
40+
<!-- Forbidden fields for tracker -->
41+
<assert test="not(pv:tilt)">
42+
Tracker rack must not have tilt element
43+
</assert>
44+
<assert test="not(pv:azimuth)">
45+
Tracker rack must not have azimuth element
46+
</assert>
47+
<assert test="not(pv:slope)">
48+
Tracker rack must not have slope element
49+
</assert>
50+
<assert test="not(pv:height_above_ground)">
51+
Tracker rack must not have height_above_ground element
52+
</assert>
53+
</rule>
54+
</pattern>
55+
56+
</schema>

0 commit comments

Comments
 (0)