• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5 ******************************************************************************
6 * Copyright (C) 2009-2011, International Business Machines Corporation and   *
7 * others. All Rights Reserved.                                               *
8 ******************************************************************************
9 */
10 
11 package ohos.global.icu.impl.duration.impl;
12 
13 import java.util.Arrays;
14 
15 import ohos.global.icu.impl.duration.TimeUnit;
16 import ohos.global.icu.impl.duration.impl.DataRecord.ECountVariant;
17 import ohos.global.icu.impl.duration.impl.DataRecord.EDecimalHandling;
18 import ohos.global.icu.impl.duration.impl.DataRecord.EFractionHandling;
19 import ohos.global.icu.impl.duration.impl.DataRecord.EGender;
20 import ohos.global.icu.impl.duration.impl.DataRecord.EHalfPlacement;
21 import ohos.global.icu.impl.duration.impl.DataRecord.EHalfSupport;
22 import ohos.global.icu.impl.duration.impl.DataRecord.ENumberSystem;
23 import ohos.global.icu.impl.duration.impl.DataRecord.EPluralization;
24 import ohos.global.icu.impl.duration.impl.DataRecord.EUnitVariant;
25 import ohos.global.icu.impl.duration.impl.DataRecord.EZeroHandling;
26 import ohos.global.icu.impl.duration.impl.DataRecord.ScopeData;
27 
28 
29 /**
30  * PeriodFormatterData provides locale-specific data used to format
31  * relative dates and times, and convenience api to access it.
32  *
33  * An instance of PeriodFormatterData is usually created by requesting
34  * data for a given locale from an PeriodFormatterDataService.
35  * @hide exposed on OHOS
36  */
37 public class PeriodFormatterData {
38   final DataRecord dr;
39   String localeName;
40 
41   // debug
42   public static boolean trace = false;
43 
PeriodFormatterData(String localeName, DataRecord dr)44   public PeriodFormatterData(String localeName, DataRecord dr) {
45     this.dr = dr;
46     this.localeName = localeName;
47     if(localeName == null) {
48         throw new NullPointerException("localename is null");
49     }
50 //    System.err.println("** localeName is " + localeName);
51     if (dr == null) {
52 //      Thread.dumpStack();
53       throw new NullPointerException("data record is null");
54     }
55   }
56 
57   // none - chinese (all forms the same)
58   // plural - english, special form for 1
59   // dual - special form for 1 and 2
60   // paucal - russian, special form for 1, for 2-4 and n > 20 && n % 10 == 2-4
61   // rpt_dual_few - slovenian, special form for 1, 2, 3-4 and n as above
62   // hebrew, dual plus singular form for years > 11
63   // arabic, dual, plus singular form for all terms > 10
64 
65   /**
66    * Return the pluralization format used by this locale.
67    * @return the pluralization format
68    */
pluralization()69   public int pluralization() {
70     return dr.pl;
71   }
72 
73   /**
74    * Return true if zeros are allowed in the display.
75    * @return true if zeros should be allowed
76    */
allowZero()77   public boolean allowZero() {
78     return dr.allowZero;
79   }
80 
weeksAloneOnly()81   public boolean weeksAloneOnly() {
82     return dr.weeksAloneOnly;
83   }
84 
useMilliseconds()85   public int useMilliseconds() {
86     return dr.useMilliseconds;
87   }
88 
89   /**
90    * Append the appropriate prefix to the string builder, depending on whether and
91    * how a limit and direction are to be displayed.
92    *
93    * @param tl how and whether to display the time limit
94    * @param td how and whether to display the time direction
95    * @param sb the string builder to which to append the text
96    * @return true if a following digit will require a digit prefix
97    */
appendPrefix(int tl, int td, StringBuffer sb)98   public boolean appendPrefix(int tl, int td, StringBuffer sb) {
99     if (dr.scopeData != null) {
100       int ix = tl * 3 + td;
101       ScopeData sd = dr.scopeData[ix];
102       if (sd != null) {
103         String prefix = sd.prefix;
104         if (prefix != null) {
105           sb.append(prefix);
106           return sd.requiresDigitPrefix;
107         }
108       }
109     }
110     return false;
111   }
112 
113   /**
114    * Append the appropriate suffix to the string builder, depending on whether and
115    * how a limit and direction are to be displayed.
116    *
117    * @param tl how and whether to display the time limit
118    * @param td how and whether to display the time direction
119    * @param sb the string builder to which to append the text
120    */
appendSuffix(int tl, int td, StringBuffer sb)121   public void appendSuffix(int tl, int td, StringBuffer sb) {
122     if (dr.scopeData != null) {
123       int ix = tl * 3 + td;
124       ScopeData sd = dr.scopeData[ix];
125       if (sd != null) {
126         String suffix = sd.suffix;
127         if (suffix != null) {
128           if (trace) {
129             System.out.println("appendSuffix '" + suffix + "'");
130           }
131           sb.append(suffix);
132         }
133       }
134     }
135   }
136 
137   /**
138    * Append the count and unit to the string builder.
139    *
140    * @param unit the unit to append
141    * @param count the count of units, * 1000
142    * @param cv the format to use for displaying the count
143    * @param uv the format to use for displaying the unit
144    * @param useCountSep if false, force no separator between count and unit
145    * @param useDigitPrefix if true, use the digit prefix
146    * @param multiple true if there are multiple units in this string
147    * @param last true if this is the last unit
148    * @param wasSkipped true if the unit(s) before this were skipped
149    * @param sb the string builder to which to append the text
150    * @return true if will require skip marker
151    */
152   @SuppressWarnings("fallthrough")
appendUnit(TimeUnit unit, int count, int cv, int uv, boolean useCountSep, boolean useDigitPrefix, boolean multiple, boolean last, boolean wasSkipped, StringBuffer sb)153   public boolean appendUnit(TimeUnit unit, int count, int cv,
154                             int uv, boolean useCountSep,
155                             boolean useDigitPrefix, boolean multiple,
156                             boolean last, boolean wasSkipped,
157                             StringBuffer sb) {
158     int px = unit.ordinal();
159 
160     boolean willRequireSkipMarker = false;
161     if (dr.requiresSkipMarker != null && dr.requiresSkipMarker[px] &&
162         dr.skippedUnitMarker != null) {
163       if (!wasSkipped && last) {
164         sb.append(dr.skippedUnitMarker);
165       }
166       willRequireSkipMarker = true;
167     }
168 
169     if (uv != EUnitVariant.PLURALIZED) {
170       boolean useMedium = uv == EUnitVariant.MEDIUM;
171       String[] names = useMedium ? dr.mediumNames : dr.shortNames;
172       if (names == null || names[px] == null) {
173         names = useMedium ? dr.shortNames : dr.mediumNames;
174       }
175       if (names != null && names[px] != null) {
176         appendCount(unit, false, false, count, cv, useCountSep,
177                     names[px], last, sb); // omit suffix, ok?
178         return false; // omit skip marker
179       }
180     }
181 
182     // check cv
183     if (cv == ECountVariant.HALF_FRACTION && dr.halfSupport != null) {
184       switch (dr.halfSupport[px]) {
185         case EHalfSupport.YES: break;
186         case EHalfSupport.ONE_PLUS:
187           if (count > 1000) {
188             break;
189           }
190           // else fall through to decimal
191         case EHalfSupport.NO: {
192           count = (count / 500) * 500;  // round to 1/2
193           cv = ECountVariant.DECIMAL1;
194         } break;
195       }
196     }
197 
198     String name = null;
199     int form = computeForm(unit, count, cv, multiple && last);
200     if (form == FORM_SINGULAR_SPELLED) {
201       if (dr.singularNames == null) {
202         form = FORM_SINGULAR;
203         name = dr.pluralNames[px][form];
204       } else {
205         name = dr.singularNames[px];
206       }
207     } else if (form == FORM_SINGULAR_NO_OMIT) {
208       name = dr.pluralNames[px][FORM_SINGULAR];
209     } else if (form == FORM_HALF_SPELLED) {
210       name = dr.halfNames[px];
211     } else {
212       try {
213         name = dr.pluralNames[px][form];
214       } catch (NullPointerException e) {
215         System.out.println("Null Pointer in PeriodFormatterData["+localeName+"].au px: " + px + " form: " + form + " pn: " + Arrays.toString(dr.pluralNames));
216         throw e;
217       }
218     }
219     if (name == null) {
220       form = FORM_PLURAL;
221       name = dr.pluralNames[px][form];
222     }
223 
224     boolean omitCount =
225       (form == FORM_SINGULAR_SPELLED || form == FORM_HALF_SPELLED) ||
226       (dr.omitSingularCount && form == FORM_SINGULAR) ||
227       (dr.omitDualCount && form == FORM_DUAL);
228 
229     int suffixIndex = appendCount(unit, omitCount, useDigitPrefix, count, cv,
230                                   useCountSep, name, last, sb);
231     if (last && suffixIndex >= 0) {
232       String suffix = null;
233       if (dr.rqdSuffixes != null && suffixIndex < dr.rqdSuffixes.length) {
234         suffix = dr.rqdSuffixes[suffixIndex];
235       }
236       if (suffix == null && dr.optSuffixes != null &&
237           suffixIndex < dr.optSuffixes.length) {
238         suffix = dr.optSuffixes[suffixIndex];
239       }
240       if (suffix != null) {
241         sb.append(suffix);
242       }
243     }
244     return willRequireSkipMarker;
245   }
246 
247   /**
248    * Append a count to the string builder.
249    *
250    * @param unit the unit
251    * @param count the count
252    * @param cv the format to use for displaying the count
253    * @param useSep whether to use the count separator, if available
254    * @param name the term name
255    * @param last true if this is the last unit to be formatted
256    * @param sb the string builder to which to append the text
257    * @return index to use if might have required or optional suffix, or -1 if none required
258    */
appendCount(TimeUnit unit, boolean omitCount, boolean useDigitPrefix, int count, int cv, boolean useSep, String name, boolean last, StringBuffer sb)259   public int appendCount(TimeUnit unit, boolean omitCount,
260                          boolean useDigitPrefix,
261                          int count, int cv, boolean useSep,
262                          String name, boolean last, StringBuffer sb) {
263     if (cv == ECountVariant.HALF_FRACTION && dr.halves == null) {
264       cv = ECountVariant.INTEGER;
265     }
266 
267     if (!omitCount && useDigitPrefix && dr.digitPrefix != null) {
268       sb.append(dr.digitPrefix);
269     }
270 
271     int index = unit.ordinal();
272     switch (cv) {
273       case ECountVariant.INTEGER: {
274         if (!omitCount) {
275           appendInteger(count/1000, 1, 10, sb);
276         }
277       } break;
278 
279       case ECountVariant.INTEGER_CUSTOM: {
280         int val = count / 1000;
281         // only custom names we have for now
282         if (unit == TimeUnit.MINUTE &&
283             (dr.fiveMinutes != null || dr.fifteenMinutes != null)) {
284           if (val != 0 && val % 5 == 0) {
285             if (dr.fifteenMinutes != null && (val == 15 || val == 45)) {
286               val = val == 15 ? 1 : 3;
287               if (!omitCount) appendInteger(val, 1, 10, sb);
288               name = dr.fifteenMinutes;
289               index = 8; // hack
290               break;
291             }
292             if (dr.fiveMinutes != null) {
293               val = val / 5;
294               if (!omitCount) appendInteger(val, 1, 10, sb);
295               name = dr.fiveMinutes;
296               index = 9; // hack
297               break;
298             }
299           }
300         }
301         if (!omitCount) appendInteger(val, 1, 10, sb);
302       } break;
303 
304       case ECountVariant.HALF_FRACTION: {
305         // 0, 1/2, 1, 1-1/2...
306         int v = count / 500;
307         if (v != 1) {
308           if (!omitCount) appendCountValue(count, 1, 0, sb);
309         }
310         if ((v & 0x1) == 1) {
311           // hack, using half name
312           if (v == 1 && dr.halfNames != null && dr.halfNames[index] != null) {
313             sb.append(name);
314             return last ? index : -1;
315           }
316 
317           int solox = v == 1 ? 0 : 1;
318           if (dr.genders != null && dr.halves.length > 2) {
319             if (dr.genders[index] == EGender.F) {
320               solox += 2;
321             }
322           }
323           int hp = dr.halfPlacements == null
324               ? EHalfPlacement.PREFIX
325               : dr.halfPlacements[solox & 0x1];
326           String half = dr.halves[solox];
327           String measure = dr.measures == null ? null : dr.measures[index];
328           switch (hp) {
329             case EHalfPlacement.PREFIX:
330               sb.append(half);
331               break;
332             case EHalfPlacement.AFTER_FIRST: {
333               if (measure != null) {
334                 sb.append(measure);
335                 sb.append(half);
336                 if (useSep && !omitCount) {
337                   sb.append(dr.countSep);
338                 }
339                 sb.append(name);
340               } else { // ignore sep completely
341                 sb.append(name);
342                 sb.append(half);
343                 return last ? index : -1; // might use suffix
344               }
345             } return -1; // exit early
346             case EHalfPlacement.LAST: {
347               if (measure != null) {
348                 sb.append(measure);
349               }
350               if (useSep && !omitCount) {
351                 sb.append(dr.countSep);
352               }
353               sb.append(name);
354               sb.append(half);
355             } return last ? index : -1; // might use suffix
356           }
357         }
358       } break;
359       default: {
360         int decimals = 1;
361         switch (cv) {
362           case ECountVariant.DECIMAL2: decimals = 2; break;
363           case ECountVariant.DECIMAL3: decimals = 3; break;
364           default: break;
365         }
366         if (!omitCount) appendCountValue(count, 1, decimals, sb);
367       } break;
368     }
369     if (!omitCount && useSep) {
370       sb.append(dr.countSep);
371     }
372     if (!omitCount && dr.measures != null && index < dr.measures.length) {
373       String measure = dr.measures[index];
374       if (measure != null) {
375         sb.append(measure);
376       }
377     }
378     sb.append(name);
379     return last ? index : -1;
380   }
381 
382   /**
383    * Append a count value to the builder.
384    *
385    * @param count the count
386    * @param integralDigits the number of integer digits to display
387    * @param decimalDigits the number of decimal digits to display, <= 3
388    * @param sb the string builder to which to append the text
389    */
appendCountValue(int count, int integralDigits, int decimalDigits, StringBuffer sb)390   public void appendCountValue(int count, int integralDigits,
391                                int decimalDigits, StringBuffer sb) {
392     int ival = count / 1000;
393     if (decimalDigits == 0) {
394       appendInteger(ival, integralDigits, 10, sb);
395       return;
396     }
397 
398     if (dr.requiresDigitSeparator && sb.length() > 0) {
399       sb.append(' ');
400     }
401     appendDigits(ival, integralDigits, 10, sb);
402     int dval = count % 1000;
403     if (decimalDigits == 1) {
404       dval /= 100;
405     } else if (decimalDigits == 2) {
406       dval /= 10;
407     }
408     sb.append(dr.decimalSep);
409     appendDigits(dval, decimalDigits, decimalDigits, sb);
410     if (dr.requiresDigitSeparator) {
411       sb.append(' ');
412     }
413   }
414 
appendInteger(int num, int mindigits, int maxdigits, StringBuffer sb)415   public void appendInteger(int num, int mindigits, int maxdigits,
416                             StringBuffer sb) {
417     if (dr.numberNames != null && num < dr.numberNames.length) {
418       String name = dr.numberNames[num];
419       if (name != null) {
420         sb.append(name);
421         return;
422       }
423     }
424 
425     if (dr.requiresDigitSeparator && sb.length() > 0) {
426       sb.append(' ');
427     }
428     switch (dr.numberSystem) {
429       case ENumberSystem.DEFAULT: appendDigits(num, mindigits, maxdigits, sb); break;
430       case ENumberSystem.CHINESE_TRADITIONAL: sb.append(
431           Utils.chineseNumber(num, Utils.ChineseDigits.TRADITIONAL)); break;
432       case ENumberSystem.CHINESE_SIMPLIFIED: sb.append(
433           Utils.chineseNumber(num, Utils.ChineseDigits.SIMPLIFIED)); break;
434       case ENumberSystem.KOREAN: sb.append(
435           Utils.chineseNumber(num, Utils.ChineseDigits.KOREAN)); break;
436     }
437     if (dr.requiresDigitSeparator) {
438       sb.append(' ');
439     }
440   }
441 
442   /**
443    * Append digits to the string builder, using this.zero for '0' etc.
444    *
445    * @param num the integer to append
446    * @param mindigits the minimum number of digits to append
447    * @param maxdigits the maximum number of digits to append
448    * @param sb the string builder to which to append the text
449    */
appendDigits(long num, int mindigits, int maxdigits, StringBuffer sb)450   public void appendDigits(long num, int mindigits, int maxdigits,
451                            StringBuffer sb) {
452     char[] buf = new char[maxdigits];
453     int ix = maxdigits;
454     while (ix > 0 && num > 0) {
455       buf[--ix] = (char)(dr.zero + (num % 10));
456       num /= 10;
457     }
458     for (int e = maxdigits - mindigits; ix > e;) {
459       buf[--ix] = dr.zero;
460     }
461     sb.append(buf, ix, maxdigits - ix);
462   }
463 
464   /**
465    * Append a marker for skipped units internal to a string.
466    * @param sb the string builder to which to append the text
467    */
appendSkippedUnit(StringBuffer sb)468   public void appendSkippedUnit(StringBuffer sb) {
469     if (dr.skippedUnitMarker != null) {
470       sb.append(dr.skippedUnitMarker);
471     }
472   }
473 
474   /**
475    * Append the appropriate separator between units
476    *
477    * @param unit the unit to which to append the separator
478    * @param afterFirst true if this is the first unit formatted
479    * @param beforeLast true if this is the next-to-last unit to be formatted
480    * @param sb the string builder to which to append the text
481    * @return true if a prefix will be required before a following unit
482    */
appendUnitSeparator(TimeUnit unit, boolean longSep, boolean afterFirst, boolean beforeLast, StringBuffer sb)483   public boolean appendUnitSeparator(TimeUnit unit, boolean longSep,
484                                      boolean afterFirst, boolean beforeLast,
485                                      StringBuffer sb) {
486     // long seps
487     // false, false "...b', '...d"
488     // false, true  "...', and 'c"
489     // true, false - "a', '...c"
490     // true, true - "a' and 'b"
491     if ((longSep && dr.unitSep != null) || dr.shortUnitSep != null) {
492       if (longSep && dr.unitSep != null) {
493         int ix = (afterFirst ? 2 : 0) + (beforeLast ? 1 : 0);
494         sb.append(dr.unitSep[ix]);
495         return dr.unitSepRequiresDP != null && dr.unitSepRequiresDP[ix];
496       }
497       sb.append(dr.shortUnitSep); // todo: investigate whether DP is required
498     }
499     return false;
500   }
501 
502   private static final int
503     FORM_PLURAL = 0,
504     FORM_SINGULAR = 1,
505     FORM_DUAL = 2,
506     FORM_PAUCAL = 3,
507     FORM_SINGULAR_SPELLED = 4, // following are not in the pluralization list
508     FORM_SINGULAR_NO_OMIT = 5, // a hack
509     FORM_HALF_SPELLED = 6;
510 
computeForm(TimeUnit unit, int count, int cv, boolean lastOfMultiple)511   private int computeForm(TimeUnit unit, int count, int cv,
512                           boolean lastOfMultiple) {
513     // first check if a particular form is forced by the countvariant.  if
514     // SO, just return that.  otherwise convert the count to an integer
515     // and use pluralization rules to determine which form to use.
516     // careful, can't assume any forms but plural exist.
517 
518     if (trace) {
519       System.err.println("pfd.cf unit: " + unit + " count: " + count + " cv: " + cv + " dr.pl: " + dr.pl);
520       Thread.dumpStack();
521     }
522     if (dr.pl == EPluralization.NONE) {
523       return FORM_PLURAL;
524     }
525     // otherwise, assume we have at least a singular and plural form
526 
527     int val = count/1000;
528 
529     switch (cv) {
530       case ECountVariant.INTEGER:
531       case ECountVariant.INTEGER_CUSTOM: {
532         // do more analysis based on floor of count
533       } break;
534       case ECountVariant.HALF_FRACTION: {
535         switch (dr.fractionHandling) {
536           case EFractionHandling.FPLURAL:
537             return FORM_PLURAL;
538 
539           case EFractionHandling.FSINGULAR_PLURAL_ANDAHALF:
540           case EFractionHandling.FSINGULAR_PLURAL: {
541             // if half-floor is 1/2, use singular
542             // else if half-floor is not integral, use plural
543             // else do more analysis
544             int v = count / 500;
545             if (v == 1) {
546               if (dr.halfNames != null && dr.halfNames[unit.ordinal()] != null) {
547                 return FORM_HALF_SPELLED;
548               }
549               return FORM_SINGULAR_NO_OMIT;
550             }
551             if ((v & 0x1) == 1) {
552               if (dr.pl == EPluralization.ARABIC && v > 21) { // hack
553                 return FORM_SINGULAR_NO_OMIT;
554               }
555               if (v == 3 && dr.pl == EPluralization.PLURAL &&
556                   dr.fractionHandling != EFractionHandling.FSINGULAR_PLURAL_ANDAHALF) {
557                 return FORM_PLURAL;
558               }
559             }
560 
561             // it will display like an integer, so do more analysis
562           } break;
563 
564           case EFractionHandling.FPAUCAL: {
565             int v = count / 500;
566             if (v == 1 || v == 3) {
567               return FORM_PAUCAL;
568             }
569             // else use integral form
570           } break;
571 
572           default:
573             throw new IllegalStateException();
574         }
575       } break;
576       default: { // for all decimals
577         switch (dr.decimalHandling) {
578           case EDecimalHandling.DPLURAL: break;
579           case EDecimalHandling.DSINGULAR: return FORM_SINGULAR_NO_OMIT;
580           case EDecimalHandling.DSINGULAR_SUBONE:
581             if (count < 1000) {
582               return FORM_SINGULAR_NO_OMIT;
583             }
584             break;
585           case EDecimalHandling.DPAUCAL:
586             if (dr.pl == EPluralization.PAUCAL) {
587               return FORM_PAUCAL;
588             }
589             break;
590           default:
591             break;
592         }
593         return FORM_PLURAL;
594       }
595     }
596 
597     // select among pluralization forms
598     if (trace && count == 0) {
599       System.err.println("EZeroHandling = " + dr.zeroHandling);
600     }
601     if (count == 0 && dr.zeroHandling == EZeroHandling.ZSINGULAR) {
602       return FORM_SINGULAR_SPELLED;
603     }
604 
605     int form = FORM_PLURAL;
606     switch(dr.pl) {
607       case EPluralization.NONE: break; // never get here
608       case EPluralization.PLURAL: {
609         if (val == 1) {
610           form = FORM_SINGULAR_SPELLED; // defaults to form_singular if no spelled forms
611         }
612       } break;
613       case EPluralization.DUAL: {
614         if (val == 2) {
615           form = FORM_DUAL;
616         } else if (val == 1) {
617           form = FORM_SINGULAR;
618         }
619       } break;
620       case EPluralization.PAUCAL: {
621         int v = val;
622         v = v % 100;
623         if (v > 20) {
624           v = v % 10;
625         }
626         if (v == 1) {
627           form = FORM_SINGULAR;
628         } else if (v > 1 && v < 5) {
629           form = FORM_PAUCAL;
630         }
631       } break;
632         /*
633       case EPluralization.RPT_DUAL_FEW: {
634         int v = val;
635         if (v > 20) {
636           v = v % 10;
637         }
638         if (v == 1) {
639           form = FORM_SINGULAR;
640         } else if (v == 2) {
641           form = FORM_DUAL;
642         } else if (v > 2 && v < 5) {
643           form = FORM_PAUCAL;
644         }
645       } break;
646         */
647       case EPluralization.HEBREW: {
648         if (val == 2) {
649           form = FORM_DUAL;
650         } else if (val == 1) {
651           if (lastOfMultiple) {
652             form = FORM_SINGULAR_SPELLED;
653           } else {
654             form = FORM_SINGULAR;
655           }
656         } else if (unit == TimeUnit.YEAR && val > 11) {
657           form = FORM_SINGULAR_NO_OMIT;
658         }
659       } break;
660       case EPluralization.ARABIC: {
661         if (val == 2) {
662           form = FORM_DUAL;
663         } else if (val == 1) {
664           form = FORM_SINGULAR;
665         } else if (val > 10) {
666           form = FORM_SINGULAR_NO_OMIT;
667         }
668       } break;
669       default:
670         System.err.println("dr.pl is " + dr.pl);
671         throw new IllegalStateException();
672     }
673 
674     return form;
675   }
676 }
677