@@ -686,7 +686,7 @@ function prepareCluster(cluster, context, isExtension = false) {
686686 bit : feature . $ . bit ,
687687 defaultValue : feature . $ . default ,
688688 description : feature . $ . summary ,
689- conformance : feature . $ . conformance
689+ conformance : parseFeatureConformance ( feature )
690690 }
691691
692692 ret . features . push ( f )
@@ -1638,10 +1638,11 @@ function prepareDeviceType(deviceType) {
16381638 }
16391639 if ( 'features' in include ) {
16401640 include . features [ 0 ] . feature . forEach ( ( f ) => {
1641- // Only adding madatory features for now
1642- if ( f . mandatoryConform && f . mandatoryConform [ 0 ] === '' ) {
1643- features . push ( f . $ . name )
1644- }
1641+ let conformance = parseFeatureConformance ( f )
1642+ features . push ( {
1643+ code : f . $ . code ,
1644+ conformance : conformance
1645+ } )
16451646 } )
16461647 }
16471648 ret . clusters . push ( {
@@ -2202,6 +2203,114 @@ async function parseFeatureFlags(db, packageId, featureFlags) {
22022203 )
22032204}
22042205
2206+ /**
2207+ * Parses feature conformance or an operand in feature conformance recursively from xml data.
2208+ *
2209+ * An example of parsing the conformance of 'User' device type feature:
2210+ *
2211+ * Input operand from xml data:
2212+ * {
2213+ * "$": {"code": "USR", "name": "User"},
2214+ * "mandatoryConform": [
2215+ * { "andTerm": [
2216+ * {
2217+ * "condition": [{"$": {"name": "Matter"}}],
2218+ * "orTerm": [
2219+ * { "feature": [
2220+ * { "$": {"name": "PIN"}},
2221+ * { "$": {"name": "RID"}},
2222+ * { "$": {"name": "FPG"}},
2223+ * { "$": {"name": "FACE"}}
2224+ * ]
2225+ * }
2226+ * ]
2227+ * }
2228+ * ]
2229+ * }
2230+ * ]
2231+ * }
2232+ *
2233+ * Output device type feature conformance string:
2234+ * "Matter & (PIN | RID | FPG | FACE)"
2235+ *
2236+ * @param {* } operand - The operand to be parsed.
2237+ * @returns The feature conformance string.
2238+ */
2239+ function parseFeatureConformance ( operand ) {
2240+ if ( operand . mandatoryConform ) {
2241+ let insideTerm = operand . mandatoryConform [ 0 ]
2242+ // Recurse further if insideTerm is not empty
2243+ if ( insideTerm && Object . keys ( insideTerm ) . toString ( ) != '$' ) {
2244+ return parseFeatureConformance ( operand . mandatoryConform [ 0 ] )
2245+ } else {
2246+ return 'M'
2247+ }
2248+ } else if ( operand . optionalConform ) {
2249+ let insideTerm = operand . optionalConform [ 0 ]
2250+ // check '$' key is not the only key in the object to handle special cases
2251+ // e.g. '<optionalConform choice="a" more="true"/>'
2252+ if ( insideTerm && Object . keys ( insideTerm ) . toString ( ) != '$' ) {
2253+ return `[${ parseFeatureConformance ( operand . optionalConform [ 0 ] ) } ]`
2254+ } else {
2255+ return 'O'
2256+ }
2257+ } else if ( operand . provisionalConform ) {
2258+ return 'P'
2259+ } else if ( operand . disallowConform ) {
2260+ return 'X'
2261+ } else if ( operand . deprecateConform ) {
2262+ return 'D'
2263+ } else if ( operand . feature ) {
2264+ return operand . feature [ 0 ] . $ . name
2265+ } else if ( operand . condition ) {
2266+ return operand . condition [ 0 ] . $ . name
2267+ } else if ( operand . otherwiseConform ) {
2268+ return Object . entries ( operand . otherwiseConform [ 0 ] )
2269+ . map ( ( [ key , value ] ) => parseFeatureConformance ( { [ key ] : value } ) )
2270+ . join ( ', ' )
2271+ } else if ( operand . notTerm ) {
2272+ let notTerms = parseFeatureConformance ( operand . notTerm [ 0 ] )
2273+ // need to surround notTerms with '()' if it contains multiple terms
2274+ // e.g. !(A | B) or !(A & B)
2275+ return notTerms . includes ( '&' ) || notTerms . includes ( '|' )
2276+ ? `!(${ notTerms } )`
2277+ : `!${ notTerms } `
2278+ } else if ( operand . andTerm ) {
2279+ return parseAndOrConformanceTerms ( operand . andTerm , '&' )
2280+ } else if ( operand . orTerm ) {
2281+ return parseAndOrConformanceTerms ( operand . orTerm , '|' )
2282+ } else {
2283+ return ''
2284+ }
2285+ }
2286+
2287+ /**
2288+ * Helper function to parse andTerm or orTerm from xml data
2289+ * @param {* } operand
2290+ * @param {* } joinChar
2291+ * @returns feature conformance string
2292+ */
2293+ function parseAndOrConformanceTerms ( operand , joinChar ) {
2294+ // when joining multiple orTerms inside andTerms, we need to
2295+ // surround them with '()', vice versa for andTerms inside orTerms
2296+ // e.g. A & (B | C) or A | (B & C)
2297+ let oppositeChar = joinChar === '&' ? '|' : '&'
2298+ let oppositeTerm = joinChar === '&' ? 'orTerm' : 'andTerm'
2299+
2300+ return Object . entries ( operand [ 0 ] )
2301+ . map ( ( [ key , value ] ) => {
2302+ if ( key == 'feature' || key == 'condition' ) {
2303+ return value . map ( ( operand ) => operand . $ . name ) . join ( ` ${ joinChar } ` )
2304+ } else if ( key == oppositeTerm ) {
2305+ let terms = parseFeatureConformance ( { [ key ] : value } )
2306+ return terms . includes ( oppositeChar ) ? `(${ terms } )` : terms
2307+ } else {
2308+ return ''
2309+ }
2310+ } )
2311+ . join ( ` ${ joinChar } ` )
2312+ }
2313+
22052314/**
22062315 * Inside the `zcl.json` can be a `featureFlags` key, which is
22072316 * a general purpose object. It contains keys, that map to objects.
0 commit comments