@ifc-lite/bcf 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/ids-camera.d.ts +115 -0
- package/dist/ids-camera.d.ts.map +1 -0
- package/dist/ids-camera.js +266 -0
- package/dist/ids-camera.js.map +1 -0
- package/dist/ids-reporter.d.ts +17 -13
- package/dist/ids-reporter.d.ts.map +1 -1
- package/dist/ids-reporter.js +32 -64
- package/dist/ids-reporter.js.map +1 -1
- package/dist/reader-components.d.ts +1 -1
- package/dist/reader-components.d.ts.map +1 -1
- package/dist/reader-components.js +49 -10
- package/dist/reader-components.js.map +1 -1
- package/dist/reader.js +28 -30
- package/dist/reader.js.map +1 -1
- package/dist/types.d.ts +30 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/viewpoint.d.ts +10 -0
- package/dist/viewpoint.d.ts.map +1 -1
- package/dist/viewpoint.js +12 -0
- package/dist/viewpoint.js.map +1 -1
- package/dist/writer-camera.d.ts.map +1 -1
- package/dist/writer-camera.js +30 -1
- package/dist/writer-camera.js.map +1 -1
- package/dist/writer.d.ts.map +1 -1
- package/dist/writer.js +93 -79
- package/dist/writer.js.map +1 -1
- package/dist/xml-text.d.ts +62 -5
- package/dist/xml-text.d.ts.map +1 -1
- package/dist/xml-text.js +131 -8
- package/dist/xml-text.js.map +1 -1
- package/dist/xsd-required-string.d.ts +38 -0
- package/dist/xsd-required-string.d.ts.map +1 -0
- package/dist/xsd-required-string.js +158 -0
- package/dist/xsd-required-string.js.map +1 -0
- package/package.json +2 -2
package/dist/xml-text.js
CHANGED
|
@@ -2,24 +2,79 @@
|
|
|
2
2
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
/**
|
|
5
|
-
* Reading text
|
|
5
|
+
* Reading text out of BCF's XML, and escaping it back in.
|
|
6
6
|
*
|
|
7
7
|
* Split out of `reader.ts` so the component parsers can share it without a
|
|
8
|
-
* cycle. `
|
|
9
|
-
*
|
|
8
|
+
* cycle. `escapeXml` here is the inverse of `unescapeXml`, and the two are
|
|
9
|
+
* pinned against each other in `writer.test.ts`.
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
12
|
* Extract a simple element value from XML
|
|
13
13
|
*
|
|
14
|
-
* Values are unescaped so
|
|
15
|
-
*
|
|
14
|
+
* Values are unescaped so {@link escapeXml} round-trips correctly (see
|
|
15
|
+
* escapeXml/unescapeXml regression: & < > " ' in titles/descriptions/
|
|
16
16
|
* comments must come back exactly as written, not as literal entities).
|
|
17
|
+
*
|
|
18
|
+
* The content between the tags is read with {@link decodeXmlCharData}
|
|
19
|
+
* rather than a bare `[^<]*` match, so a CDATA-wrapped value (whose content
|
|
20
|
+
* starts with the literal `<![CDATA[`) is read instead of silently
|
|
21
|
+
* rejected -- the same defect `parseLabels` was fixed for, but present on
|
|
22
|
+
* every other caller of this function (Title, Comment, and the rest).
|
|
23
|
+
* `decodeXmlCharData` still returns `null`, and this still returns
|
|
24
|
+
* `undefined`, when the content holds real child-element markup.
|
|
17
25
|
*/
|
|
18
26
|
export function extractElement(content, elementName) {
|
|
19
|
-
const match = content.match(new RegExp(`<${elementName}>([
|
|
20
|
-
|
|
27
|
+
const match = content.match(new RegExp(`<${elementName}>([\\s\\S]*?)<\\/${elementName}>`));
|
|
28
|
+
if (match?.[1] === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
const decoded = decodeXmlCharData(match[1]);
|
|
31
|
+
return decoded ?? undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse a captured `xs:boolean` value per its lexical space
|
|
35
|
+
* `{true, false, 1, 0}` (whiteSpace=collapse, so leading/trailing
|
|
36
|
+
* whitespace is ignored; matched case-sensitively, per the XSD's literal
|
|
37
|
+
* lexical space -- BCF markup never emits `True`/`TRUE`).
|
|
38
|
+
*
|
|
39
|
+
* Does NOT decide what an *absent* attribute means, nor a *blank* one: a
|
|
40
|
+
* value that collapses to the empty string is schema-invalid for
|
|
41
|
+
* `xs:boolean` and is treated the same as absent, not as recognised-false or
|
|
42
|
+
* as garbage -- so every caller checks `raw === undefined` (or blank, after
|
|
43
|
+
* a `.trim()`) itself and supplies its own absent-case default, which
|
|
44
|
+
* differs by call site (`DefaultVisibility` leans true on BCF 2.1, false on
|
|
45
|
+
* 3.0; the others are `undefined`-optional or lean false). This unifies the
|
|
46
|
+
* four hand-rolled xs:boolean parses this package had (`parseVisibility`,
|
|
47
|
+
* `parseViewSetupHints`'s `flag()`, and the two `isExternal` sites in
|
|
48
|
+
* `reader.ts`), which is exactly the drift #3713 warns about: one behaviour,
|
|
49
|
+
* several hand-rolled implementations, each patched independently.
|
|
50
|
+
*
|
|
51
|
+
* A present value outside the lexical space (garbage, e.g. `"yes"`) resolves
|
|
52
|
+
* to `ifUnrecognized` rather than a hardcoded pick, because callers
|
|
53
|
+
* genuinely disagree on which way an unrecognised value should lean.
|
|
54
|
+
*
|
|
55
|
+
* @param raw a non-blank, already-captured attribute or element value (the
|
|
56
|
+
* caller has already ruled out absent/blank)
|
|
57
|
+
*/
|
|
58
|
+
export function parseXsBoolean(raw, { ifUnrecognized }) {
|
|
59
|
+
const trimmed = raw.trim();
|
|
60
|
+
if (trimmed === 'true' || trimmed === '1')
|
|
61
|
+
return true;
|
|
62
|
+
if (trimmed === 'false' || trimmed === '0')
|
|
63
|
+
return false;
|
|
64
|
+
return ifUnrecognized;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Escape XML special characters
|
|
68
|
+
*/
|
|
69
|
+
export function escapeXml(str) {
|
|
70
|
+
return str
|
|
71
|
+
.replace(/&/g, '&')
|
|
72
|
+
.replace(/</g, '<')
|
|
73
|
+
.replace(/>/g, '>')
|
|
74
|
+
.replace(/"/g, '"')
|
|
75
|
+
.replace(/'/g, ''');
|
|
21
76
|
}
|
|
22
|
-
/** The five entities
|
|
77
|
+
/** The five entities {@link escapeXml} produces. */
|
|
23
78
|
const NAMED_ENTITIES = {
|
|
24
79
|
lt: '<',
|
|
25
80
|
gt: '>',
|
|
@@ -52,4 +107,72 @@ export function unescapeXml(str) {
|
|
|
52
107
|
: whole;
|
|
53
108
|
});
|
|
54
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Extract topic `<Labels>` values, tolerant of both BCF markup.xsd shapes.
|
|
112
|
+
*
|
|
113
|
+
* BCF 2.1 repeats the label element itself: `<Labels>Structural</Labels>
|
|
114
|
+
* <Labels>Urgent</Labels>`. BCF 3.0 instead wraps ONE `<Labels>` container
|
|
115
|
+
* around repeated `<Label>` children: `<Labels><Label>Structural</Label>
|
|
116
|
+
* <Label>Urgent</Label></Labels>`. A regex that only matched the 2.1 shape
|
|
117
|
+
* (immediate non-tag text) silently dropped every label from a conformant
|
|
118
|
+
* 3.0 archive, since its `<Labels>` is immediately followed by a nested `<`,
|
|
119
|
+
* not text. This walks each `<Labels>` block and prefers nested `<Label>`
|
|
120
|
+
* children when present, falling back to the 2.1 direct-text shape.
|
|
121
|
+
*
|
|
122
|
+
* Label content goes through {@link decodeXmlCharData} rather than a bare
|
|
123
|
+
* `[^<]*` match, so a CDATA label (`<Label><![CDATA[Urgent & Important]]>
|
|
124
|
+
* </Label>` — whose content starts with `<`) is read instead of dropped.
|
|
125
|
+
*/
|
|
126
|
+
export function parseLabels(topicContent) {
|
|
127
|
+
const labels = [];
|
|
128
|
+
// One emptiness rule for both shapes, so `<Labels><Label></Label></Labels>`
|
|
129
|
+
// and `<Labels></Labels>` agree. The 3.0 branch used to push
|
|
130
|
+
// decodeXmlCharData's '' straight through while the 2.1 branch skipped it,
|
|
131
|
+
// so only the 3.0 shape produced a phantom empty label. Whitespace-only
|
|
132
|
+
// content is NOT empty and is kept: markup.xsd types both elements as
|
|
133
|
+
// xs:string (whiteSpace=preserve), so ' ' is a legal value, and that is
|
|
134
|
+
// what the 2.1 path has always returned.
|
|
135
|
+
const push = (raw) => {
|
|
136
|
+
const text = decodeXmlCharData(raw);
|
|
137
|
+
if (text !== null && text.length > 0)
|
|
138
|
+
labels.push(text);
|
|
139
|
+
};
|
|
140
|
+
for (const block of topicContent.matchAll(/<Labels>([\s\S]*?)<\/Labels>/g)) {
|
|
141
|
+
const inner = block[1];
|
|
142
|
+
const nested = [...inner.matchAll(/<Label>([\s\S]*?)<\/Label>/g)];
|
|
143
|
+
if (nested.length > 0) {
|
|
144
|
+
for (const label of nested)
|
|
145
|
+
push(label[1]);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
push(inner);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return labels;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Decode element content that should be XML character data, tolerating CDATA
|
|
155
|
+
* sections.
|
|
156
|
+
*
|
|
157
|
+
* Per the XML spec, CDATA content is literal — `&` inside CDATA stays
|
|
158
|
+
* `&` — while text outside CDATA is entity-decoded via
|
|
159
|
+
* {@link unescapeXml}. Returns `null` when the content holds real markup (a
|
|
160
|
+
* `<` outside any CDATA section): that is child elements, not character
|
|
161
|
+
* data, and the caller should not treat it as a text value.
|
|
162
|
+
*/
|
|
163
|
+
function decodeXmlCharData(raw) {
|
|
164
|
+
let out = '';
|
|
165
|
+
let last = 0;
|
|
166
|
+
for (const m of raw.matchAll(/<!\[CDATA\[([\s\S]*?)\]\]>/g)) {
|
|
167
|
+
const text = raw.slice(last, m.index);
|
|
168
|
+
if (text.includes('<'))
|
|
169
|
+
return null;
|
|
170
|
+
out += unescapeXml(text) + m[1];
|
|
171
|
+
last = m.index + m[0].length;
|
|
172
|
+
}
|
|
173
|
+
const tail = raw.slice(last);
|
|
174
|
+
if (tail.includes('<'))
|
|
175
|
+
return null;
|
|
176
|
+
return out + unescapeXml(tail);
|
|
177
|
+
}
|
|
55
178
|
//# sourceMappingURL=xml-text.js.map
|
package/dist/xml-text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xml-text.js","sourceRoot":"","sources":["../src/xml-text.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;GAMG;AAEH
|
|
1
|
+
{"version":3,"file":"xml-text.js","sourceRoot":"","sources":["../src/xml-text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a date-ish string to a valid `xs:dateTime`, or `undefined` if it
|
|
3
|
+
* can't be made into one. A value already matching `xs:dateTime` is returned
|
|
4
|
+
* trimmed but otherwise UNCHANGED -- never reformatted or shifted. A bare
|
|
5
|
+
* `YYYY-MM-DD` becomes midnight UTC on that date. Anything else (an empty
|
|
6
|
+
* string, free text, a malformed timestamp) is unparseable and comes back
|
|
7
|
+
* `undefined`; the caller decides whether that means "omit the element" or
|
|
8
|
+
* "refuse to write".
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeXsdDateTime(value: string): string | undefined;
|
|
11
|
+
export declare const XML_WHITESPACE_ONLY: RegExp;
|
|
12
|
+
/**
|
|
13
|
+
* A required `xs:dateTime` on its way into an archive: normalized (a bare
|
|
14
|
+
* date becomes midnight UTC; an already-valid dateTime passes through
|
|
15
|
+
* unchanged), escaped -- or an error. `minOccurs="1"` with no default means
|
|
16
|
+
* there is no valid element to omit here, so an absent or unparseable value
|
|
17
|
+
* both refuse rather than write something invalid or invented.
|
|
18
|
+
*/
|
|
19
|
+
export declare function xsdDateTime(value: string | undefined, element: string, where: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* An optional `xs:dateTime` on its way into an archive: normalized the same
|
|
22
|
+
* way as `xsdDateTime`, but `undefined` (never a throw) for anything absent
|
|
23
|
+
* or unparseable. `minOccurs="0"` makes omitting the element schema-valid,
|
|
24
|
+
* and that is always preferable to writing invalid text that poisons the
|
|
25
|
+
* whole file -- the caller checks the return value and skips the element
|
|
26
|
+
* when it is `undefined`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function xsdOptionalDateTime(value: string | undefined): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* A required author-like string on its way into an archive, escaped -- or an error.
|
|
31
|
+
*
|
|
32
|
+
* BCF 2.1 uses `UserIdType`, an unrestricted `xs:string`, so an explicitly
|
|
33
|
+
* supplied empty or whitespace-only author is schema-valid. BCF 3.0 replaced
|
|
34
|
+
* it with `NonEmptyOrBlankString`, whose collapsed value must be non-empty.
|
|
35
|
+
* Absence is invalid in both versions because the element itself is required.
|
|
36
|
+
*/
|
|
37
|
+
export declare function xsdRequiredString(value: string | undefined, element: string, where: string, version: '2.1' | '3.0'): string;
|
|
38
|
+
//# sourceMappingURL=xsd-required-string.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xsd-required-string.d.ts","sourceRoot":"","sources":["../src/xsd-required-string.ts"],"names":[],"mappings":"AA8EA;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOtE;AAQD,eAAO,MAAM,mBAAmB,QAAiB,CAAC;AAElD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkB7F;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIjF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,KAAK,GAAG,KAAK,GACrB,MAAM,CASR"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Write-side guards for `markup.xsd` elements declared `minOccurs="1"` with
|
|
6
|
+
* no schema default: `Topic/CreationDate`, `Comment/Date`,
|
|
7
|
+
* `Topic/CreationAuthor`, `Topic/ModifiedAuthor` and `Comment/Author`. Also
|
|
8
|
+
* the normalizer shared by every `xs:dateTime` element in `markup.xsd`,
|
|
9
|
+
* required or not: `Topic/CreationDate`, `Topic/ModifiedDate`,
|
|
10
|
+
* `Topic/DueDate`, `Comment/Date`, `Comment/ModifiedDate` and
|
|
11
|
+
* `Header/File/Date`.
|
|
12
|
+
*
|
|
13
|
+
* Split out of `writer.ts`, which was pushing its module-size budget. Mirrors
|
|
14
|
+
* `numeric.ts`'s split for the numeric xsd guards (`xsdDouble`/`xsdInt`).
|
|
15
|
+
*
|
|
16
|
+
* `reader.ts` no longer invents a value for these fields when a source
|
|
17
|
+
* `.bcfzip` omits them (a wall-clock `CreationDate`, a placeholder `'Unknown'`
|
|
18
|
+
* author) -- so a `BCFTopic`/`BCFComment` read back from a non-conformant
|
|
19
|
+
* archive can arrive with any of them unset. Inventing one HERE, on write,
|
|
20
|
+
* would be that same fabrication under a different name; dropping the element
|
|
21
|
+
* entirely hands the caller an archive that already fails the schema. So both
|
|
22
|
+
* refuse, the same way the `TopicType`/`TopicStatus` check in `writer.ts`
|
|
23
|
+
* does for BCF 3.0.
|
|
24
|
+
*
|
|
25
|
+
* `xs:dateTime` is stricter than the callers ever validated: an HTML
|
|
26
|
+
* `<input type="date">` yields a bare `YYYY-MM-DD`, which every one of the
|
|
27
|
+
* elements above rejects as "not a valid value of the atomic type
|
|
28
|
+
* xs:dateTime" (buildingSMART/BCF-XML `markup.xsd`, confirmed against a real
|
|
29
|
+
* reported archive -- issue #3612's `markup.bcf` carried exactly this on
|
|
30
|
+
* `DueDate`). `normalizeXsdDateTime` turns a bare date into midnight UTC,
|
|
31
|
+
* passes an already-valid `xs:dateTime` through UNCHANGED (no reformatting,
|
|
32
|
+
* no timezone shift), and returns `undefined` for anything else.
|
|
33
|
+
*/
|
|
34
|
+
import { escapeXml } from './xml-text.js';
|
|
35
|
+
// A full `xs:dateTime`: date, mandatory 'T', time, optional fractional
|
|
36
|
+
// seconds, optional timezone (Z or ±HH:MM). The lexical match only decides
|
|
37
|
+
// pass-through-unchanged vs normalize-or-reject and never reformats a value
|
|
38
|
+
// that already matches; `isCalendarValid` below then rejects lexically-valid
|
|
39
|
+
// but calendar-impossible values the xs:dateTime VALUE space excludes.
|
|
40
|
+
const XSD_DATETIME_RE = /^-?(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
|
|
41
|
+
// A bare xs:date, e.g. what an HTML `<input type="date">` yields.
|
|
42
|
+
const BARE_DATE_RE = /^-?(\d{4,})-(\d{2})-(\d{2})$/;
|
|
43
|
+
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
44
|
+
/**
|
|
45
|
+
* Value-space check behind the lexical regexes: the xs:dateTime/xs:date VALUE
|
|
46
|
+
* space excludes calendar-impossible lexemes (month 13, February 30, minute
|
|
47
|
+
* 99, a timezone beyond ±14:00), and a schema validator rejects them — so a
|
|
48
|
+
* writer that emitted one would hand the caller an archive that fails
|
|
49
|
+
* validation, exactly what this module exists to refuse. Hour 24 is legal in
|
|
50
|
+
* XSD only as 24:00:00 (end of day).
|
|
51
|
+
*/
|
|
52
|
+
function isCalendarValid(m, hasTime) {
|
|
53
|
+
const year = Number(m[1]);
|
|
54
|
+
const month = Number(m[2]);
|
|
55
|
+
const day = Number(m[3]);
|
|
56
|
+
if (month < 1 || month > 12 || day < 1)
|
|
57
|
+
return false;
|
|
58
|
+
const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
59
|
+
const maxDay = month === 2 && leap ? 29 : DAYS_IN_MONTH[month - 1];
|
|
60
|
+
if (day > maxDay)
|
|
61
|
+
return false;
|
|
62
|
+
if (!hasTime)
|
|
63
|
+
return true;
|
|
64
|
+
const hour = Number(m[4]);
|
|
65
|
+
const minute = Number(m[5]);
|
|
66
|
+
const second = Number(m[6]);
|
|
67
|
+
if (minute > 59 || second > 59)
|
|
68
|
+
return false;
|
|
69
|
+
if (hour > 24 || (hour === 24 && (minute !== 0 || second !== 0)))
|
|
70
|
+
return false;
|
|
71
|
+
const tz = m[7];
|
|
72
|
+
if (tz !== undefined && tz !== 'Z') {
|
|
73
|
+
const tzHour = Number(tz.slice(1, 3));
|
|
74
|
+
const tzMinute = Number(tz.slice(4, 6));
|
|
75
|
+
if (tzHour > 14 || tzMinute > 59 || (tzHour === 14 && tzMinute !== 0))
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Normalize a date-ish string to a valid `xs:dateTime`, or `undefined` if it
|
|
82
|
+
* can't be made into one. A value already matching `xs:dateTime` is returned
|
|
83
|
+
* trimmed but otherwise UNCHANGED -- never reformatted or shifted. A bare
|
|
84
|
+
* `YYYY-MM-DD` becomes midnight UTC on that date. Anything else (an empty
|
|
85
|
+
* string, free text, a malformed timestamp) is unparseable and comes back
|
|
86
|
+
* `undefined`; the caller decides whether that means "omit the element" or
|
|
87
|
+
* "refuse to write".
|
|
88
|
+
*/
|
|
89
|
+
export function normalizeXsdDateTime(value) {
|
|
90
|
+
const trimmed = value.trim();
|
|
91
|
+
const dateTime = trimmed.match(XSD_DATETIME_RE);
|
|
92
|
+
if (dateTime)
|
|
93
|
+
return isCalendarValid(dateTime, true) ? trimmed : undefined;
|
|
94
|
+
const bareDate = trimmed.match(BARE_DATE_RE);
|
|
95
|
+
if (bareDate)
|
|
96
|
+
return isCalendarValid(bareDate, false) ? `${trimmed}T00:00:00Z` : undefined;
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
// BCF 3.0's markup.xsd types `Topic/@TopicType` and `Topic/@TopicStatus` as
|
|
100
|
+
// `NonEmptyOrBlankString`: after XML whitespace (#x9, #xA, #xD, #x20) is
|
|
101
|
+
// collapsed, the value must have length >= 1. A value that is present but
|
|
102
|
+
// consists entirely of XML whitespace collapses to nothing and is therefore
|
|
103
|
+
// as invalid as an absent one -- which is why the guards below reuse it, and
|
|
104
|
+
// why `writer.ts`'s own TopicType/TopicStatus checks import it too.
|
|
105
|
+
export const XML_WHITESPACE_ONLY = /^[\t\n\r ]*$/;
|
|
106
|
+
/**
|
|
107
|
+
* A required `xs:dateTime` on its way into an archive: normalized (a bare
|
|
108
|
+
* date becomes midnight UTC; an already-valid dateTime passes through
|
|
109
|
+
* unchanged), escaped -- or an error. `minOccurs="1"` with no default means
|
|
110
|
+
* there is no valid element to omit here, so an absent or unparseable value
|
|
111
|
+
* both refuse rather than write something invalid or invented.
|
|
112
|
+
*/
|
|
113
|
+
export function xsdDateTime(value, element, where) {
|
|
114
|
+
if (!value || XML_WHITESPACE_ONLY.test(value)) {
|
|
115
|
+
throw new Error(`BCF requires ${element} (${where} has none). markup.xsd declares it ` +
|
|
116
|
+
`minOccurs="1" with no default, and a time the source never stated is ` +
|
|
117
|
+
`not the writer's to invent. Set it before writing.`);
|
|
118
|
+
}
|
|
119
|
+
const normalized = normalizeXsdDateTime(value);
|
|
120
|
+
if (normalized === undefined) {
|
|
121
|
+
throw new Error(`BCF requires ${element} to be a valid xs:dateTime (${where} has ` +
|
|
122
|
+
`"${value}", which is neither a dateTime nor a bare YYYY-MM-DD date). ` +
|
|
123
|
+
`markup.xsd types it xs:dateTime with no default, so an unparseable ` +
|
|
124
|
+
`value is not the writer's to invent a fallback for. Fix it before writing.`);
|
|
125
|
+
}
|
|
126
|
+
return escapeXml(normalized);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* An optional `xs:dateTime` on its way into an archive: normalized the same
|
|
130
|
+
* way as `xsdDateTime`, but `undefined` (never a throw) for anything absent
|
|
131
|
+
* or unparseable. `minOccurs="0"` makes omitting the element schema-valid,
|
|
132
|
+
* and that is always preferable to writing invalid text that poisons the
|
|
133
|
+
* whole file -- the caller checks the return value and skips the element
|
|
134
|
+
* when it is `undefined`.
|
|
135
|
+
*/
|
|
136
|
+
export function xsdOptionalDateTime(value) {
|
|
137
|
+
if (!value || XML_WHITESPACE_ONLY.test(value))
|
|
138
|
+
return undefined;
|
|
139
|
+
const normalized = normalizeXsdDateTime(value);
|
|
140
|
+
return normalized === undefined ? undefined : escapeXml(normalized);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* A required author-like string on its way into an archive, escaped -- or an error.
|
|
144
|
+
*
|
|
145
|
+
* BCF 2.1 uses `UserIdType`, an unrestricted `xs:string`, so an explicitly
|
|
146
|
+
* supplied empty or whitespace-only author is schema-valid. BCF 3.0 replaced
|
|
147
|
+
* it with `NonEmptyOrBlankString`, whose collapsed value must be non-empty.
|
|
148
|
+
* Absence is invalid in both versions because the element itself is required.
|
|
149
|
+
*/
|
|
150
|
+
export function xsdRequiredString(value, element, where, version) {
|
|
151
|
+
if (value === undefined || (version === '3.0' && XML_WHITESPACE_ONLY.test(value))) {
|
|
152
|
+
throw new Error(`BCF requires ${element} (${where} has none). markup.xsd declares it ` +
|
|
153
|
+
`minOccurs="1" with no default, and a value the source never stated is ` +
|
|
154
|
+
`not the writer's to invent. Set it before writing.`);
|
|
155
|
+
}
|
|
156
|
+
return escapeXml(value);
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=xsd-required-string.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xsd-required-string.js","sourceRoot":"","sources":["../src/xsd-required-string.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifc-lite/bcf",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "BIM Collaboration Format (BCF) support for IFC-Lite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"jszip": "^3.10.0",
|
|
17
|
-
"@ifc-lite/encoding": "^2.
|
|
17
|
+
"@ifc-lite/encoding": "^2.2.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"typescript": "^6.0.3",
|