2121namespace SysML2 . NET . CodeGenerator . HandleBarHelpers
2222{
2323 using System ;
24+ using System . Collections . Generic ;
2425 using System . Globalization ;
2526 using System . Linq ;
2627 using System . Text ;
@@ -36,6 +37,7 @@ namespace SysML2.NET.CodeGenerator.HandleBarHelpers
3637 using uml4net . Classification ;
3738 using uml4net . CommonStructure ;
3839 using uml4net . StructuredClassifiers ;
40+ using uml4net . Values ;
3941
4042 /// <summary>
4143 /// A handlebars block helper for the <see cref="IProperty"/> interface
@@ -1120,6 +1122,151 @@ public static void RegisterPropertyHelper(this IHandlebars handlebars)
11201122
11211123 return property . Type is IClassifier { IsAbstract : true } ;
11221124 } ) ;
1125+
1126+ handlebars . RegisterHelper ( "Property.QueryHasOwnedConstraints" , ( _ , arguments ) =>
1127+ {
1128+ if ( arguments . Length != 1 || arguments [ 0 ] is not IProperty property )
1129+ {
1130+ throw new HandlebarsException ( "{{Property.QueryHasOwnedConstraints}} expects a single IProperty argument" ) ;
1131+ }
1132+
1133+ return HasRenderableConstraint ( property . QueryOwnedConstraints ( ) ) ;
1134+ } ) ;
1135+
1136+ handlebars . RegisterHelper ( "Operation.QueryHasOwnedConstraints" , ( _ , arguments ) =>
1137+ {
1138+ if ( arguments . Length != 1 || arguments [ 0 ] is not IOperation operation )
1139+ {
1140+ throw new HandlebarsException ( "{{Operation.QueryHasOwnedConstraints}} expects a single IOperation argument" ) ;
1141+ }
1142+
1143+ return HasRenderableConstraint ( operation . QueryOwnedConstraints ( ) ) ;
1144+ } ) ;
1145+
1146+ handlebars . RegisterHelper ( "Property.WriteOwnedRulesAsRemarksBlock" , ( writer , _ , arguments ) =>
1147+ {
1148+ if ( arguments . Length != 1 || arguments [ 0 ] is not IProperty property )
1149+ {
1150+ throw new HandlebarsException ( "{{Property.WriteOwnedRulesAsRemarksBlock}} expects a single IProperty argument" ) ;
1151+ }
1152+
1153+ writer . WriteSafeString ( BuildOwnedRulesRemarksBlock ( property . QueryOwnedConstraints ( ) ) ) ;
1154+ } ) ;
1155+
1156+ handlebars . RegisterHelper ( "Operation.WriteOwnedRulesAsRemarksBlock" , ( writer , _ , arguments ) =>
1157+ {
1158+ if ( arguments . Length != 1 || arguments [ 0 ] is not IOperation operation )
1159+ {
1160+ throw new HandlebarsException ( "{{Operation.WriteOwnedRulesAsRemarksBlock}} expects a single IOperation argument" ) ;
1161+ }
1162+
1163+ writer . WriteSafeString ( BuildOwnedRulesRemarksBlock ( operation . QueryOwnedConstraints ( ) ) ) ;
1164+ } ) ;
1165+ }
1166+
1167+ /// <summary>
1168+ /// Builds a complete XML <c><remarks></c> block listing every constraint body carried by
1169+ /// the supplied <see cref="IConstraint"/> sequence, labelled by language. Returns the empty
1170+ /// string when no constraint carries an <see cref="IOpaqueExpression"/> body.
1171+ /// </summary>
1172+ /// <param name="constraints">The constraints to render.</param>
1173+ /// <returns>
1174+ /// A multi-line string starting with <c>/// <remarks></c> and ending with <c>/// </remarks></c>,
1175+ /// each line prefixed for direct emission inside the Extend template; empty when nothing to emit.
1176+ /// </returns>
1177+ /// <summary>
1178+ /// Returns <see langword="true"/> when at least one constraint in <paramref name="constraints"/>
1179+ /// carries an <see cref="IOpaqueExpression"/> with at least one non-blank body line — i.e. when
1180+ /// <see cref="BuildOwnedRulesRemarksBlock"/> would emit a non-empty <c><remarks></c> block.
1181+ /// </summary>
1182+ /// <param name="constraints">The constraints to evaluate.</param>
1183+ /// <returns>Whether the helpers would produce any output for these constraints.</returns>
1184+ private static bool HasRenderableConstraint ( IEnumerable < IConstraint > constraints )
1185+ {
1186+ foreach ( var constraint in constraints )
1187+ {
1188+ var opaqueExpression = constraint . Specification ? . OfType < IOpaqueExpression > ( ) . FirstOrDefault ( ) ;
1189+
1190+ if ( opaqueExpression ? . Body != null && opaqueExpression . Body . Any ( body => ! string . IsNullOrWhiteSpace ( body ) ) )
1191+ {
1192+ return true ;
1193+ }
1194+ }
1195+
1196+ return false ;
1197+ }
1198+
1199+ private static string BuildOwnedRulesRemarksBlock ( IEnumerable < IConstraint > constraints )
1200+ {
1201+ var entries = new List < ( string Language , string Body ) > ( ) ;
1202+
1203+ foreach ( var constraint in constraints )
1204+ {
1205+ var opaqueExpression = constraint . Specification ? . OfType < IOpaqueExpression > ( ) . FirstOrDefault ( ) ;
1206+
1207+ if ( opaqueExpression == null || opaqueExpression . Body == null )
1208+ {
1209+ continue ;
1210+ }
1211+
1212+ var bodies = opaqueExpression . Body ;
1213+ var languages = opaqueExpression . Language ;
1214+
1215+ for ( var index = 0 ; index < bodies . Count ; index ++ )
1216+ {
1217+ if ( string . IsNullOrWhiteSpace ( bodies [ index ] ) )
1218+ {
1219+ continue ;
1220+ }
1221+
1222+ var language = languages != null && index < languages . Count && ! string . IsNullOrWhiteSpace ( languages [ index ] )
1223+ ? languages [ index ] . Trim ( )
1224+ : "Constraint" ;
1225+
1226+ entries . Add ( ( language , bodies [ index ] . Trim ( ) ) ) ;
1227+ }
1228+ }
1229+
1230+ if ( entries . Count == 0 )
1231+ {
1232+ return string . Empty ;
1233+ }
1234+
1235+ var sb = new StringBuilder ( ) ;
1236+ sb . AppendLine ( "/// <remarks>" ) ;
1237+
1238+ for ( var index = 0 ; index < entries . Count ; index ++ )
1239+ {
1240+ var ( language , body ) = entries [ index ] ;
1241+
1242+ sb . AppendLine ( $ "/// { EscapeXml ( language ) } :") ;
1243+ sb . AppendLine ( "/// <code>" ) ;
1244+
1245+ foreach ( var line in body . Replace ( "\r \n " , "\n " ) . Replace ( '\r ' , '\n ' ) . Split ( '\n ' ) )
1246+ {
1247+ sb . AppendLine ( $ "/// { EscapeXml ( line . TrimEnd ( ) ) } ") ;
1248+ }
1249+
1250+ sb . AppendLine ( "/// </code>" ) ;
1251+ }
1252+
1253+ sb . Append ( "/// </remarks>" ) ;
1254+
1255+ return sb . ToString ( ) ;
1256+ }
1257+
1258+ /// <summary>
1259+ /// Replaces XML-significant characters with their entity equivalents so the result is safe to
1260+ /// embed inside an XML doc comment.
1261+ /// </summary>
1262+ /// <param name="value">The string to escape.</param>
1263+ /// <returns>The escaped string.</returns>
1264+ private static string EscapeXml ( string value )
1265+ {
1266+ return value
1267+ . Replace ( "&" , "&" )
1268+ . Replace ( "<" , "<" )
1269+ . Replace ( ">" , ">" ) ;
11231270 }
11241271
11251272 /// <summary>
0 commit comments