package org.unicode.cldr.util; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.unicode.cldr.draft.FileUtilities; import org.unicode.cldr.test.CheckCLDR; import org.unicode.cldr.test.CheckCLDR.CheckStatus; import org.unicode.cldr.test.CheckCLDR.CheckStatus.Subtype; import org.unicode.cldr.test.CheckCoverage; import org.unicode.cldr.test.CheckNew; import org.unicode.cldr.test.CoverageLevel2; import org.unicode.cldr.test.OutdatedPaths; import org.unicode.cldr.tool.Option; import org.unicode.cldr.tool.Option.Options; import org.unicode.cldr.tool.ToolConstants; import org.unicode.cldr.util.CLDRFile.Status; import org.unicode.cldr.util.PathHeader.PageId; import org.unicode.cldr.util.PathHeader.SectionId; import org.unicode.cldr.util.StandardCodes.LocaleCoverageType; import com.ibm.icu.dev.util.CollectionUtilities; import com.ibm.icu.impl.Relation; import com.ibm.icu.impl.Row; import com.ibm.icu.impl.Row.R2; import com.ibm.icu.text.Collator; import com.ibm.icu.text.NumberFormat; import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.ICUUncheckedIOException; import com.ibm.icu.util.Output; import com.ibm.icu.util.ULocale; /** * Provides a HTML tables showing the important issues for vetters to review for * a given locale. See the main for an example. Most elements have CSS styles, * allowing for customization of the display. * * @author markdavis */ public class VettingViewer { private static boolean SHOW_SUBTYPES = true; // CldrUtility.getProperty("SHOW_SUBTYPES", "false").equals("true"); private static final String CONNECT_PREFIX = "₍_"; private static final String CONNECT_SUFFIX = "₎"; private static final String TH_AND_STYLES = ""; private static final String SPLIT_CHAR = "\uFFFE"; private static final boolean SUPPRESS = true; private static final String TEST_PATH = "//ldml/localeDisplayNames/territories/territory[@type=\"SX\"]"; private static final double NANOSECS = 1000000000.0; private static final boolean TESTING = CldrUtility.getProperty("TEST", false); private static final boolean SHOW_ALL = CldrUtility.getProperty("SHOW", true); public static final Pattern ALT_PROPOSED = PatternCache.get("\\[@alt=\"[^\"]*proposed"); public static Set OK_IF_VOTED = EnumSet.of(Subtype.sameAsEnglishOrCode, Subtype.sameAsEnglishOrCode); public enum Choice { /** * There is a console-check error */ error('E', "Error", "The Survey Tool detected an error in the winning value.", 1), /** * My choice is not the winning item */ weLost( 'L', "Losing", "The value that your organization chose (overall) is either not the winning value, or doesn’t have enough votes to be approved. " + "This might be due to a dispute between members of your organization.", 2), /** * There is a dispute. */ notApproved('P', "Provisional", "There are not enough votes for this item to be approved (and used).", 3), /** * There is a dispute. */ hasDispute('D', "Disputed", "Different organizations are choosing different values. " + "Please review to approve or reach consensus.", 4), /** * There is a console-check warning */ warning('W', "Warning", "The Survey Tool detected a warning about the winning value.", 5), /** * The English value for the path changed AFTER the current value for * the locale. */ englishChanged('U', "English Changed", "The English value has changed in CLDR, but the corresponding value for your language has not. Check if any changes are needed in your language.", 6), /** * The value changed from the last version of CLDR */ changedOldValue('N', "New", "The winning value was altered from the last-released CLDR value. (Informational)", 7), /** * Given the users' coverage, some items are missing. */ missingCoverage( 'M', "Missing", "Your current coverage level requires the item to be present. (During the vetting phase, this is informational: you can’t add new values.)", 8), // /** // * There is a console-check error // */ // other('O', "Other", "Everything else."), ; public final char abbreviation; public final String buttonLabel; public final String description; public final int order; Choice(char abbreviation, String buttonLabel, String description, int order) { this.abbreviation = abbreviation; this.buttonLabel = TransliteratorUtilities.toHTML.transform(buttonLabel); this.description = TransliteratorUtilities.toHTML.transform(description); this.order = order; } public static T appendDisplay(Set choices, String htmlMessage, T target) { try { boolean first = true; for (Choice item : choices) { if (first) { first = false; } else { target.append(", "); } item.appendDisplay(htmlMessage, target); } return target; } catch (IOException e) { throw new ICUUncheckedIOException(e); // damn'd checked // exceptions } } private void appendDisplay(String htmlMessage, T target) throws IOException { target.append("") .append(buttonLabel) .append("*"); } public static Choice fromString(String i) { try { return valueOf(i); } catch (NullPointerException e) { throw e; } catch (RuntimeException e) { if (i.isEmpty()) { throw e; } int cp = i.codePointAt(0); for (Choice choice : Choice.values()) { if (cp == choice.abbreviation) { return choice; } } throw e; } } public static Appendable appendRowStyles(Set choices, Appendable target) { try { if (choices.contains(Choice.changedOldValue)) { int x = 0; // debugging } target.append("hide"); for (Choice item : choices) { target.append(' ').append("vv").append(Character.toLowerCase(item.abbreviation)); } return target; } catch (IOException e) { throw new ICUUncheckedIOException(e); // damn'd checked // exceptions } } } public static OutdatedPaths getOutdatedPaths() { return outdatedPaths; } static private PathHeader.Factory pathTransform; static final Pattern breaks = PatternCache.get("\\|"); static final OutdatedPaths outdatedPaths = new OutdatedPaths(); // private static final UnicodeSet NEEDS_PERCENT_ESCAPED = new UnicodeSet("[[\\u0000-\\u009F]-[a-zA-z0-9]]"); // private static final Transform percentEscape = new Transform() { // @Override // public String transform(String source) { // StringBuilder buffer = new StringBuilder(); // buffer.setLength(0); // for (int cp : CharSequences.codePoints(source)) { // if (NEEDS_PERCENT_ESCAPED.contains(cp)) { // buffer.append('%').append(Utility.hex(cp, 2)); // } else { // buffer.appendCodePoint(cp); // } // } // return buffer.toString(); // } // }; /** * See VoteResolver getStatusForOrganization to see how this is computed. */ public enum VoteStatus { /** * The value for the path is either contributed or approved, and * the user's organization didn't vote. (see class def for null user) */ ok_novotes, /** * The value for the path is either contributed or approved, and * the user's organization chose the winning value. (see class def for null user) */ ok, /** * The user's organization chose the winning value for the path, but * that value is neither contributed nor approved. (see class def for null user) */ provisionalOrWorse, /** * The user's organization's choice is not winning. There may be * insufficient votes to overcome a previously approved value, or other * organizations may be voting against it. (see class def for null user) */ losing, /** * There is a dispute, meaning more than one item with votes, or the item with votes didn't win. */ disputed } /** * @author markdavis * * @param */ public static interface UsersChoice { /** * Return the value that the user's organization (as a whole) voted for, * or null if none of the users in the organization voted for the path.
* NOTE: Would be easier if this were a method on CLDRFile. * NOTE: if user = null, then it must return the absolute winning value. * * @param locale */ public String getWinningValueForUsersOrganization(CLDRFile cldrFile, String path, T user); /** * * Return the vote status * NOTE: if user = null, then it must disregard the user and never return losing. See VoteStatus. * * @param locale */ public VoteStatus getStatusForUsersOrganization(CLDRFile cldrFile, String path, T user); } public static interface ErrorChecker { enum Status { ok, error, warning } /** * Initialize an error checker with a cldrFile. MUST be called before * any getErrorStatus. */ public Status initErrorStatus(CLDRFile cldrFile); /** * Return the detailed CheckStatus information. */ public List getErrorCheckStatus(String path, String value); /** * Return the status, and append the error message to the status * message. If there are any errors, then the warnings are not included. */ public Status getErrorStatus(String path, String value, StringBuilder statusMessage); /** * Return the status, and append the error message to the status * message, and get the subtypes. If there are any errors, then the warnings are not included. */ public Status getErrorStatus(String path, String value, StringBuilder statusMessage, EnumSet outputSubtypes); } public static class NoErrorStatus implements ErrorChecker { @Override public Status initErrorStatus(CLDRFile cldrFile) { return Status.ok; } @Override public List getErrorCheckStatus(String path, String value) { return Collections.emptyList(); } @Override public Status getErrorStatus(String path, String value, StringBuilder statusMessage) { return Status.ok; } @Override public Status getErrorStatus(String path, String value, StringBuilder statusMessage, EnumSet outputSubtypes) { return Status.ok; } } public static class DefaultErrorStatus implements ErrorChecker { private CheckCLDR checkCldr; private HashMap options = new HashMap(); private ArrayList result = new ArrayList(); private CLDRFile cldrFile; private Factory factory; public DefaultErrorStatus(Factory cldrFactory) { this.factory = cldrFactory; } @Override public Status initErrorStatus(CLDRFile cldrFile) { this.cldrFile = cldrFile; options = new HashMap(); result = new ArrayList(); checkCldr = CheckCLDR.getCheckAll(factory, ".*"); checkCldr.setCldrFileToCheck(cldrFile, options, result); return Status.ok; } @Override public List getErrorCheckStatus(String path, String value) { String fullPath = cldrFile.getFullXPath(path); ArrayList result2 = new ArrayList(); checkCldr.check(path, fullPath, value, options, result2); return result2; } @Override public Status getErrorStatus(String path, String value, StringBuilder statusMessage) { return getErrorStatus(path, value, statusMessage, null); } @Override public Status getErrorStatus(String path, String value, StringBuilder statusMessage, EnumSet outputSubtypes) { Status result0 = Status.ok; StringBuilder errorMessage = new StringBuilder(); String fullPath = cldrFile.getFullXPath(path); checkCldr.check(path, fullPath, value, options, result); for (CheckStatus checkStatus : result) { final CheckCLDR cause = checkStatus.getCause(); if (cause instanceof CheckCoverage || cause instanceof CheckNew) { continue; } CheckStatus.Type statusType = checkStatus.getType(); if (statusType.equals(CheckStatus.errorType)) { // throw away any accumulated warning messages if (result0 == Status.warning) { errorMessage.setLength(0); if (outputSubtypes != null) { outputSubtypes.clear(); } } result0 = Status.error; if (outputSubtypes != null) { outputSubtypes.add(checkStatus.getSubtype()); } appendToMessage(checkStatus.getMessage(), checkStatus.getSubtype(), errorMessage); } else if (result0 != Status.error && statusType.equals(CheckStatus.warningType)) { result0 = Status.warning; // accumulate all the warning messages if (outputSubtypes != null) { outputSubtypes.add(checkStatus.getSubtype()); } appendToMessage(checkStatus.getMessage(), checkStatus.getSubtype(), errorMessage); } } if (result0 != Status.ok) { appendToMessage(errorMessage, statusMessage); } return result0; } } private final Factory cldrFactory; private final Factory cldrFactoryOld; private final CLDRFile englishFile; //private final CLDRFile oldEnglishFile; private final UsersChoice userVoteStatus; private final SupplementalDataInfo supplementalDataInfo; private final String lastVersionTitle; private final String currentWinningTitle; //private final PathDescription pathDescription; private ErrorChecker errorChecker; // new private final Set defaultContentLocales; // NoErrorStatus(); // // // for // testing /** * Create the Vetting Viewer. * * @param supplementalDataInfo * @param cldrFactory * @param cldrFactoryOld * @param lastVersionTitle * The title of the last released version of CLDR. * @param currentWinningTitle * The title of the next version of CLDR to be released. */ public VettingViewer(SupplementalDataInfo supplementalDataInfo, Factory cldrFactory, Factory cldrFactoryOld, UsersChoice userVoteStatus, String lastVersionTitle, String currentWinningTitle) { super(); this.cldrFactory = cldrFactory; this.cldrFactoryOld = cldrFactoryOld; englishFile = cldrFactory.make("en", true); if (pathTransform == null) { pathTransform = PathHeader.getFactory(englishFile); } //oldEnglishFile = cldrFactoryOld.make("en", true); this.userVoteStatus = userVoteStatus; this.supplementalDataInfo = supplementalDataInfo; this.defaultContentLocales = supplementalDataInfo.getDefaultContentLocales(); this.lastVersionTitle = lastVersionTitle; this.currentWinningTitle = currentWinningTitle; //Map>> starredPaths = new HashMap>>(); //Map extras = new HashMap(); reasonsToPaths = Relation.of(new HashMap>(), HashSet.class); //this.pathDescription = new PathDescription(supplementalDataInfo, englishFile, extras, starredPaths, // PathDescription.ErrorHandling.CONTINUE); errorChecker = new DefaultErrorStatus(cldrFactory); } public class WritingInfo implements Comparable { public final PathHeader codeOutput; public final Set problems; public final String htmlMessage; public WritingInfo(PathHeader pretty, EnumSet problems, CharSequence htmlMessage) { super(); this.codeOutput = pretty; this.problems = Collections.unmodifiableSet(problems.clone()); this.htmlMessage = htmlMessage.toString(); } @Override public int compareTo(WritingInfo other) { return codeOutput.compareTo(other.codeOutput); } public String getUrl(CLDRLocale locale) { return urls.forPathHeader(locale, codeOutput); } } // public void generateHtmlErrorTablesOld(Appendable output, EnumSet choices, String localeID, T user, Level // usersLevel) { // generateHtmlErrorTablesOld(output, choices, localeID, user, usersLevel, false); // } // private void generateHtmlErrorTablesOld(Appendable output, EnumSet choices, String localeID, T user, // Level usersLevel, boolean showAll) { // // // first gather the relevant paths // // each one will be marked with the choice that it triggered. // // CLDRFile sourceFile = cldrFactory.make(localeID, true); // Matcher altProposed = PatternCache.get("\\[@alt=\"[^\"]*proposed").matcher(""); // EnumSet problems = EnumSet.noneOf(Choice.class); // // // Initialize // CoverageLevel2 coverage = CoverageLevel2.getInstance(supplementalDataInfo, localeID); // CLDRFile lastSourceFile = null; // try { // lastSourceFile = cldrFactoryOld.make(localeID, true); // } catch (Exception e) { // } // // // set the following only where needed. // Status status = null; // // Map options = null; // List result = null; // // for (Choice choice : choices) { // switch (choice) { // case changedOldValue: // break; // case missingCoverage: // status = new Status(); // break; // case englishChanged: // break; // case error: // case warning: // errorChecker.initErrorStatus(sourceFile); // break; // case weLost: // case hasDispute: // //case other: // break; // default: // System.out.println(choice + " not implemented yet"); // } // } // // // now look through the paths // // Relation, WritingInfo> sorted = Relation.of(new TreeMap, // Set>(), TreeSet.class); // // Counter problemCounter = new Counter(); // StringBuilder htmlMessage = new StringBuilder(); // StringBuilder statusMessage = new StringBuilder(); // // for (String path : sourceFile) { // progressCallback.nudge(); // Let the user know we're moving along. // // // note that the value might be missing! // // // make sure we only look at the real values // if (altProposed.reset(path).find()) { // continue; // } // // if (path.contains("/exemplarCharacters") || path.contains("/references")) { // continue; // } // // Level level = coverage.getLevel(path); // // // skip anything above the requested level // if (level.compareTo(usersLevel) > 0) { // continue; // } // // String value = sourceFile.getWinningValue(path); // // problems.clear(); // htmlMessage.setLength(0); // boolean haveError = false; // VoteStatus voteStatus = null; // // for (Choice choice : choices) { // switch (choice) { // case changedOldValue: // String oldValue = lastSourceFile == null ? null : lastSourceFile.getWinningValue(path); // if (oldValue != null && !oldValue.equals(value)) { // problems.add(choice); // problemCounter.increment(choice); // } // break; // case missingCoverage: // if (showAll && !localeID.equals("root")) { // if (isMissing(sourceFile, path, status)) { // problems.add(choice); // problemCounter.increment(choice); // } // } // break; // case englishChanged: // if (outdatedPaths.isOutdated(localeID, path) // // || // // !CharSequences.equals(englishFile.getWinningValue(path), // // oldEnglishFile.getWinningValue(path)) // ) { // // the outdated paths compares the base value, before // // data submission, // // so see if the value changed. // String lastValue = lastSourceFile == null ? null : lastSourceFile.getWinningValue(path); // if (CharSequences.equals(value, lastValue)) { // problems.add(choice); // problemCounter.increment(choice); // } // } // break; // case error: // case warning: // if (haveError) { // break; // } // statusMessage.setLength(0); // ErrorChecker.Status errorStatus = errorChecker.getErrorStatus(path, value, statusMessage); // if ((choice == Choice.error && errorStatus == ErrorChecker.Status.error) // || (choice == Choice.warning && errorStatus == ErrorChecker.Status.warning)) { // if (choice == Choice.warning) { // // for now, suppress cases where the English changed // if (outdatedPaths.isOutdated(localeID, path)) { // break; // } // } // problems.add(choice); // appendToMessage(statusMessage, htmlMessage); // problemCounter.increment(choice); // haveError = true; // break; // } // break; // case weLost: // if (voteStatus == null) { // voteStatus = userVoteStatus.getStatusForUsersOrganization(sourceFile, path, user); // } // switch (voteStatus) { // case provisionalOrWorse: // case losing: // if (choice == Choice.weLost) { // problems.add(choice); // problemCounter.increment(choice); // String usersValue = userVoteStatus.getWinningValueForUsersOrganization(sourceFile, path, user); // // appendToMessage(usersValue, testMessage); // } // break; // } // break; // case hasDispute: // if (voteStatus == null) { // voteStatus = userVoteStatus.getStatusForUsersOrganization(sourceFile, path, user); // } // if (voteStatus == VoteStatus.disputed) { // problems.add(choice); // problemCounter.increment(choice); // String usersValue = userVoteStatus.getWinningValueForUsersOrganization(sourceFile, path, user); // if (usersValue != null) { // // appendToMessage(usersValue, testMessage); // } // } // break; // } // } // if (!problems.isEmpty()) { // showAll || // // if (showAll && problems.isEmpty()) { // // problems.add(Choice.other); // // problemCounter.increment(Choice.other); // // } // reasonsToPaths.clear(); // // appendToMessage("level:" + level.toString(), testMessage); // // final String description = // // pathDescription.getDescription(path, value, level, null); // // if (!reasonsToPaths.isEmpty()) { // // appendToMessage(level + " " + // // TransliteratorUtilities.toHTML.transform(reasonsToPaths.toString()), // // testMessage); // // } // // if (description != null && !description.equals("SKIP")) { // // appendToMessage(TransliteratorUtilities.toHTML.transform(description), // // testMessage); // // } // //final String prettyPath = pathTransform.getPrettyPath(path); // // String[] pathParts = breaks.split(prettyPath); // // String section = pathParts.length == 3 ? pathParts[0] : // // "Unknown"; // // String subsection = pathParts.length == 3 ? pathParts[1] : // // "Unknown"; // // String code = pathParts.length == 3 ? pathParts[2] : pretty; // // PathHeader pretty = pathTransform.fromPath(path); // //String[] pathParts = breaks.split(pretty); // // String sectionOutput = pathParts.length == 3 ? pathParts[0] : "Unknown"; // // String subsectionOutput = pathParts.length == 3 ? pathParts[1] : "Unknown"; // // String codeOutput = pathParts.length == 3 ? pathParts[2] : pretty; // // R2 group = Row.of(pretty.getSectionId(), pretty.getPageId()); // // sorted.put(group, new WritingInfo(pretty, problems, htmlMessage)); // } // } // // // now write the results out // writeTables(output, sourceFile, lastSourceFile, sorted, problemCounter, choices, localeID, showAll); // } /** * Show a table of values, filtering according to the choices here and in * the constructor. * * @param output * @param choices * See the class description for more information. * @param localeId * @param user * @param usersLevel * @param nonVettingPhase */ public void generateHtmlErrorTables(Appendable output, EnumSet choices, String localeID, T user, Level usersLevel, boolean nonVettingPhase, boolean quick) { // Gather the relevant paths // each one will be marked with the choice that it triggered. Relation, WritingInfo> sorted = Relation.of( new TreeMap, Set>(), TreeSet.class); CLDRFile sourceFile = cldrFactory.make(localeID, true); // Initialize CLDRFile lastSourceFile = null; if (!quick) { try { lastSourceFile = cldrFactoryOld.make(localeID, true); } catch (Exception e) { } } FileInfo fileInfo = new FileInfo().getFileInfo(sourceFile, lastSourceFile, sorted, choices, localeID, nonVettingPhase, user, usersLevel, quick); // now write the results out writeTables(output, sourceFile, lastSourceFile, sorted, choices, localeID, nonVettingPhase, fileInfo, quick); } /** * Give the list of errors * * @param output * @param choices * See the class description for more information. * @param localeId * @param user * @param usersLevel * @param nonVettingPhase */ public Relation, WritingInfo> generateFileInfoReview(Appendable output, EnumSet choices, String localeID, T user, Level usersLevel, boolean nonVettingPhase, boolean quick) { // Gather the relevant paths // each one will be marked with the choice that it triggered. Relation, WritingInfo> sorted = Relation.of( new TreeMap, Set>(), TreeSet.class); CLDRFile sourceFile = cldrFactory.make(localeID, true); // Initialize CLDRFile lastSourceFile = null; if (!quick) { try { lastSourceFile = cldrFactoryOld.make(localeID, true); } catch (Exception e) { } } FileInfo fileInfo = new FileInfo().getFileInfo(sourceFile, lastSourceFile, sorted, choices, localeID, nonVettingPhase, user, usersLevel, quick); // now write the results out return sorted; } class FileInfo { Counter problemCounter = new Counter(); Counter errorSubtypeCounter = new Counter(); Counter warningSubtypeCounter = new Counter(); EnumSet problems = EnumSet.noneOf(Choice.class); public void addAll(FileInfo other) { problemCounter.addAll(other.problemCounter); errorSubtypeCounter.addAll(other.errorSubtypeCounter); warningSubtypeCounter.addAll(other.warningSubtypeCounter); } private FileInfo getFileInfo(CLDRFile sourceFile, CLDRFile lastSourceFile, Relation, WritingInfo> sorted, EnumSet choices, String localeID, boolean nonVettingPhase, T user, Level usersLevel, boolean quick) { return this.getFileInfo(sourceFile, lastSourceFile, sorted, choices, localeID, nonVettingPhase, user, usersLevel, quick, null); } private FileInfo getFileInfo(CLDRFile sourceFile, CLDRFile lastSourceFile, Relation, WritingInfo> sorted, EnumSet choices, String localeID, boolean nonVettingPhase, T user, Level usersLevel, boolean quick, String xpath) { Status status = new Status(); errorChecker.initErrorStatus(sourceFile); Matcher altProposed = ALT_PROPOSED.matcher(""); problems = EnumSet.noneOf(Choice.class); // now look through the paths StringBuilder htmlMessage = new StringBuilder(); StringBuilder statusMessage = new StringBuilder(); EnumSet subtypes = EnumSet.noneOf(Subtype.class); Set seenSoFar = new HashSet(); boolean latin = VettingViewer.isLatinScriptLocale(sourceFile); for (String path : sourceFile.fullIterable()) { if (xpath != null && !xpath.equals(path)) continue; String value = sourceFile.getWinningValue(path); statusMessage.setLength(0); subtypes.clear(); ErrorChecker.Status errorStatus = errorChecker.getErrorStatus(path, value, statusMessage, subtypes); if (quick && errorStatus != ErrorChecker.Status.error && errorStatus != ErrorChecker.Status.warning) { //skip all values but errors and warnings if in "quick" mode continue; } if (seenSoFar.contains(path)) { continue; } seenSoFar.add(path); progressCallback.nudge(); // Let the user know we're moving along. PathHeader pretty = pathTransform.fromPath(path); if (pretty.getSurveyToolStatus() == PathHeader.SurveyToolStatus.HIDE) { continue; } // note that the value might be missing! // make sure we only look at the real values if (altProposed.reset(path).find()) { continue; } if (path.contains("/references")) { continue; } Level level = supplementalDataInfo.getCoverageLevel(path, sourceFile.getLocaleID()); // skip anything above the requested level if (level.compareTo(usersLevel) > 0) { continue; } problems.clear(); htmlMessage.setLength(0); String oldValue = lastSourceFile == null ? null : lastSourceFile.getWinningValue(path); if (choices.contains(Choice.changedOldValue)) { if (oldValue != null && !oldValue.equals(value)) { problems.add(Choice.changedOldValue); problemCounter.increment(Choice.changedOldValue); } } VoteStatus voteStatus = userVoteStatus.getStatusForUsersOrganization(sourceFile, path, user); MissingStatus missingStatus = getMissingStatus(sourceFile, path, status, latin); if (choices.contains(Choice.missingCoverage) && missingStatus == MissingStatus.ABSENT) { problems.add(Choice.missingCoverage); problemCounter.increment(Choice.missingCoverage); } boolean itemsOkIfVoted = SUPPRESS && voteStatus == VoteStatus.ok; if (!itemsOkIfVoted && outdatedPaths.isOutdated(localeID, path)) { // the outdated paths compares the base value, before // data submission, // so see if the value changed. // String lastValue = lastSourceFile == null ? null : lastSourceFile.getWinningValue(path); if (Objects.equals(value, oldValue) && choices.contains(Choice.englishChanged)) { // check to see if we voted problems.add(Choice.englishChanged); problemCounter.increment(Choice.englishChanged); } } Choice choice = errorStatus == ErrorChecker.Status.error ? Choice.error : errorStatus == ErrorChecker.Status.warning ? Choice.warning : null; if (choice == Choice.error && choices.contains(Choice.error) && (!itemsOkIfVoted || !OK_IF_VOTED.containsAll(subtypes))) { problems.add(choice); appendToMessage(statusMessage, htmlMessage); problemCounter.increment(choice); for (Subtype subtype : subtypes) { errorSubtypeCounter.increment(subtype); } } else if (choice == Choice.warning && choices.contains(Choice.warning) && (!itemsOkIfVoted || !OK_IF_VOTED.containsAll(subtypes))) { problems.add(choice); appendToMessage(statusMessage, htmlMessage); problemCounter.increment(choice); for (Subtype subtype : subtypes) { warningSubtypeCounter.increment(subtype); } } switch (voteStatus) { case losing: if (choices.contains(Choice.weLost)) { problems.add(Choice.weLost); problemCounter.increment(Choice.weLost); } String usersValue = userVoteStatus.getWinningValueForUsersOrganization(sourceFile, path, user); if (usersValue != null) { usersValue = "Losing value: <" + TransliteratorUtilities.toHTML.transform(usersValue) + ">"; appendToMessage(usersValue, htmlMessage); } break; case disputed: if (choices.contains(Choice.hasDispute)) { problems.add(Choice.hasDispute); problemCounter.increment(Choice.hasDispute); } break; case provisionalOrWorse: if (missingStatus == MissingStatus.PRESENT && choices.contains(Choice.notApproved)) { problems.add(Choice.notApproved); problemCounter.increment(Choice.notApproved); } break; default: } if (xpath != null) return this; if (!problems.isEmpty()) { // showAll || // if (showAll && problems.isEmpty()) { // problems.add(Choice.other); // problemCounter.increment(Choice.other); // } if (sorted != null) { reasonsToPaths.clear(); // final String prettyPath = pathTransform.getPrettyPath(path); // String[] pathParts = breaks.split(pretty); // String sectionOutput = pathParts.length == 3 ? pathParts[0] : "Unknown"; // String subsectionOutput = pathParts.length == 3 ? pathParts[1] : "Unknown"; // String codeOutput = pathParts.length == 3 ? pathParts[2] : pretty; R2 group = Row.of(pretty.getSectionId(), pretty.getPageId()); sorted.put(group, new WritingInfo(pretty, problems, htmlMessage)); } } } return this; } } public static final class LocalesWithExplicitLevel implements Predicate { private final Organization org; private final Level desiredLevel; public LocalesWithExplicitLevel(Organization org, Level level) { this.org = org; this.desiredLevel = level; } @Override public boolean is(String localeId) { Output output = new Output(); // For admin - return true if SOME organization has explicit coverage for the locale // TODO: Make admin pick up any locale that has a vote if (org.equals(Organization.surveytool)) { for (Organization checkorg : Organization.values()) { StandardCodes.make().getLocaleCoverageLevel(checkorg, localeId, output); if (output.value == StandardCodes.LocaleCoverageType.explicit) { return true; } } return false; } else { Level level = StandardCodes.make().getLocaleCoverageLevel(org, localeId, output); return desiredLevel == level && output.value == StandardCodes.LocaleCoverageType.explicit; } } }; public void generateSummaryHtmlErrorTables(Appendable output, EnumSet choices, Predicate includeLocale, T organization) { try { output .append("

The following summarizes the Priority Items across locales, " + "using the default coverage levels for your organization for each locale. " + "Before using, please read the instructions at " + "Priority " + "Items Summary.

\n"); StringBuilder headerRow = new StringBuilder(); headerRow .append("") .append(TH_AND_STYLES) .append("Locale") .append(TH_AND_STYLES) .append("Codes"); for (Choice choice : choices) { headerRow.append(""); choice.appendDisplay("", headerRow); headerRow.append(""); } headerRow.append("\n"); String header = headerRow.toString(); if (organization.equals(Organization.surveytool)) { writeSummaryTable(output, header, Level.COMPREHENSIVE, choices, organization); } else { for (Level level : Level.values()) { writeSummaryTable(output, header, level, choices, organization); } } } catch (IOException e) { throw new ICUUncheckedIOException(e); // dang'ed checked exceptions } } private void writeSummaryTable(Appendable output, String header, Level desiredLevel, EnumSet choices, T organization) throws IOException { Map sortedNames = new TreeMap(Collator.getInstance()); // Gather the relevant paths // Each one will be marked with the choice that it triggered. // TODO Fix HACK // We are going to ignore the predicate for now, just using the locales that have explicit coverage. // in that locale, or allow all locales for admin@ LocalesWithExplicitLevel includeLocale = new LocalesWithExplicitLevel((Organization) organization, desiredLevel); for (String localeID : cldrFactory.getAvailable()) { if (defaultContentLocales.contains(localeID) || localeID.equals("en") || !includeLocale.is(localeID)) { continue; } sortedNames.put(getName(localeID), localeID); } if (sortedNames.isEmpty()) { return; } EnumSet thingsThatRequireOldFile = EnumSet.of(Choice.englishChanged, Choice.missingCoverage, Choice.changedOldValue); EnumSet ourChoicesThatRequireOldFile = choices.clone(); ourChoicesThatRequireOldFile.retainAll(thingsThatRequireOldFile); output.append("

Level: ").append(desiredLevel.toString()).append("

"); output.append("\n"); char lastChar = ' '; Map localeNameToFileInfo = new TreeMap(); FileInfo totals = new FileInfo(); for (Entry entry : sortedNames.entrySet()) { String name = entry.getKey(); String localeID = entry.getValue(); // Initialize CLDRFile sourceFile = cldrFactory.make(localeID, true); CLDRFile lastSourceFile = null; if (!ourChoicesThatRequireOldFile.isEmpty()) { try { lastSourceFile = cldrFactoryOld.make(localeID, true); } catch (Exception e) { } } Level level = Level.MODERN; if (organization != null) { level = StandardCodes.make().getLocaleCoverageLevel(organization.toString(), localeID); } FileInfo fileInfo = new FileInfo().getFileInfo(sourceFile, lastSourceFile, null, choices, localeID, true, organization, level, false); localeNameToFileInfo.put(name, fileInfo); totals.addAll(fileInfo); char nextChar = name.charAt(0); if (lastChar != nextChar) { output.append(header); lastChar = nextChar; } writeSummaryRow(output, choices, fileInfo.problemCounter, name, localeID); if (output instanceof Writer) { ((Writer) output).flush(); } } output.append(header); writeSummaryRow(output, choices, totals.problemCounter, "Total", null); output.append("
"); if (SHOW_SUBTYPES) { showSubtypes(output, sortedNames, localeNameToFileInfo, totals, true); showSubtypes(output, sortedNames, localeNameToFileInfo, totals, false); } } private void showSubtypes(Appendable output, Map sortedNames, Map localeNameToFileInfo, FileInfo totals, boolean errors) throws IOException { output.append("

Details: ").append(errors ? "Error Types" : "Warning Types").append("

"); output.append(""); Counter subtypeCounterTotals = errors ? totals.errorSubtypeCounter : totals.warningSubtypeCounter; Set sortedBySize = subtypeCounterTotals.getKeysetSortedByCount(false); // header writeDetailHeader(subtypeCounterTotals, sortedBySize, output); // items for (Entry entry : localeNameToFileInfo.entrySet()) { Counter counter = errors ? entry.getValue().errorSubtypeCounter : entry.getValue().warningSubtypeCounter; if (counter.getTotal() == 0) { continue; } String name = entry.getKey(); //String[] names = name.split(SPLIT_CHAR); String localeID = sortedNames.get(name); output.append("").append(TH_AND_STYLES); appendNameAndCode(name, localeID, output); output.append(""); for (Subtype subtype : sortedBySize) { long count = counter.get(subtype); output.append(""); } } // subtotals writeDetailHeader(subtypeCounterTotals, sortedBySize, output); output.append("").append(TH_AND_STYLES).append("Total").append("").append(TH_AND_STYLES).append(""); for (Subtype subtype : sortedBySize) { long count = subtypeCounterTotals.get(subtype); output.append(""); } output.append("
"); if (count != 0) { output.append(nf.format(count)); } output.append("
"); if (count != 0) { output.append("").append(nf.format(count)).append(""); } output.append("
"); } private void writeDetailHeader(Counter subtypeCounterTotals, Set sortedBySize, Appendable output) throws IOException { output.append("") .append(TH_AND_STYLES).append("Name").append("") .append(TH_AND_STYLES).append("ID").append(""); for (Subtype subtype : sortedBySize) { output.append(TH_AND_STYLES).append(subtype.toString()).append(""); } } private void writeSummaryRow(Appendable output, EnumSet choices, Counter problemCounter, String name, String localeID) throws IOException { output .append("") .append(TH_AND_STYLES); if (localeID == null) { output .append("") .append(name) .append("") .append("") .append(TH_AND_STYLES); } else { appendNameAndCode(name, localeID, output); } output.append("\n"); for (Choice choice : choices) { long count = problemCounter.get(choice); output.append(""); if (localeID == null) { output.append(""); } output.append(nf.format(count)); if (localeID == null) { output.append(""); } output.append("\n"); } output.append("\n"); } private void appendNameAndCode(String name, String localeID, Appendable output) throws IOException { String[] names = name.split(SPLIT_CHAR); output .append("") .append(TransliteratorUtilities.toHTML.transform(names[0])) .append("") .append("") .append(TH_AND_STYLES) .append("") .append(names[1]) .append(""); } LanguageTagParser ltp = new LanguageTagParser(); private String getName(String localeID) { Set contents = supplementalDataInfo.getEquivalentsForLocale(localeID); // put in special character that can be split on later String name = englishFile.getName(localeID, true, CLDRFile.SHORT_ALTS) + SPLIT_CHAR + gatherCodes(contents); return name; } /** * Collapse the names {en_Cyrl, en_Cyrl_US} => en_Cyrl(_US) {en_GB, en_Latn_GB} => en(_Latn)_GB {en, en_US, en_Latn, en_Latn_US} => en(_Latn)(_US) {az_IR, az_Arab, az_Arab_IR} => az_IR, az_Arab(_IR) */ public static String gatherCodes(Set contents) { Set> source = new LinkedHashSet>(); for (String s : contents) { source.add(new LinkedHashSet(Arrays.asList(s.split("_")))); } Set> oldSource = new LinkedHashSet>(); do { // exchange source/target oldSource.clear(); oldSource.addAll(source); source.clear(); Set last = null; for (Set ss : oldSource) { if (last == null) { last = ss; } else { if (ss.containsAll(last)) { last = combine(last, ss); } else { source.add(last); last = ss; } } } source.add(last); } while (oldSource.size() != source.size()); StringBuilder b = new StringBuilder(); for (Set stringSet : source) { if (b.length() != 0) { b.append(", "); } String sep = ""; for (String string : stringSet) { if (string.startsWith(CONNECT_PREFIX)) { b.append(string + CONNECT_SUFFIX); } else { b.append(sep + string); } sep = "_"; } } return b.toString(); } private static Set combine(Set last, Set ss) { LinkedHashSet result = new LinkedHashSet(); for (String s : ss) { if (last.contains(s)) { result.add(s); } else { result.add(CONNECT_PREFIX + s); } } return result; } public enum MissingStatus { PRESENT, ALIASED, MISSING_OK, ROOT_OK, ABSENT } public static MissingStatus getMissingStatus(CLDRFile sourceFile, String path, Status status, boolean latin) { if (sourceFile == null) { return MissingStatus.ABSENT; } if ("root".equals(sourceFile.getLocaleID()) || path.startsWith("//ldml/layout/orientation/")) { return MissingStatus.MISSING_OK; } if (path.equals(TEST_PATH)) { int debug = 1; } MissingStatus result; String value = sourceFile.getStringValue(path); boolean isAliased = path.equals(status.pathWhereFound); if (value == null) { result = ValuePathStatus.isMissingOk(sourceFile, path, latin, isAliased) ? MissingStatus.MISSING_OK : MissingStatus.ABSENT; } else { String localeFound = sourceFile.getSourceLocaleID(path, status); // only count it as missing IF the (localeFound is root or codeFallback) // AND the aliasing didn't change the path if (localeFound.equals("root") || localeFound.equals(XMLSource.CODE_FALLBACK_ID) // || voteStatus == VoteStatus.provisionalOrWorse ) { result = ValuePathStatus.isMissingOk(sourceFile, path, latin, isAliased) || sourceFile.getLocaleID().equals("en") ? MissingStatus.ROOT_OK : MissingStatus.ABSENT; } else if (isAliased) { result = MissingStatus.PRESENT; // } else if (path.contains("decimalFormatLength[@type=\"long\"]") && // path.contains("pattern[@type=\"1")) { // aliased // // special case compact numbers // // // ldml/numbers/decimalFormats[@numberSystem="latn"]/decimalFormatLength[@type="long"]/decimalFormat[@type="standard"]/pattern[@type="10000000"] // result = MissingStatus.ABSENT; } else { result = MissingStatus.ALIASED; } } return result; } public static final UnicodeSet LATIN = ValuePathStatus.LATIN; public static boolean isLatinScriptLocale(CLDRFile sourceFile) { return ValuePathStatus.isLatinScriptLocale(sourceFile); } private static StringBuilder appendToMessage(CharSequence usersValue, EnumSet subtypes, StringBuilder testMessage) { if (subtypes != null) { usersValue = "<" + CollectionUtilities.join(subtypes, ", ") + "> " + usersValue; } return appendToMessage(usersValue, testMessage); } private static StringBuilder appendToMessage(CharSequence usersValue, Subtype subtype, StringBuilder testMessage) { if (subtype != null) { usersValue = "<" + subtype + "> " + usersValue; } return appendToMessage(usersValue, testMessage); } private static StringBuilder appendToMessage(CharSequence usersValue, StringBuilder testMessage) { if (usersValue.length() == 0) { return testMessage; } if (testMessage.length() != 0) { testMessage.append("
"); } return testMessage.append(usersValue); } static final NumberFormat nf = NumberFormat.getIntegerInstance(ULocale.ENGLISH); private Relation reasonsToPaths; private CLDRURLS urls = CLDRConfig.getInstance().urls(); static { nf.setGroupingUsed(true); } /** * Class that allows the relaying of progress information * * @author srl * */ public static class ProgressCallback { /** * Note any progress. This will be called before any output is printed. * It will be called approximately once per xpath. */ public void nudge() { } /** * Called when all operations are complete. */ public void done() { } } private ProgressCallback progressCallback = new ProgressCallback(); // null // instance // by // default /** * Select a new callback. Must be set before running. * * @return * */ public VettingViewer setProgressCallback(ProgressCallback newCallback) { progressCallback = newCallback; return this; } public ErrorChecker getErrorChecker() { return errorChecker; } /** * Select a new error checker. Must be set before running. * * @return * */ public VettingViewer setErrorChecker(ErrorChecker errorChecker) { this.errorChecker = errorChecker; return this; } /** * Provide the styles for inclusion into the ST <head> element. * * @return */ public static String getHeaderStyles() { return ""; } private void writeTables(Appendable output, CLDRFile sourceFile, CLDRFile lastSourceFile, Relation, WritingInfo> sorted, EnumSet choices, String localeID, boolean nonVettingPhase, FileInfo outputFileInfo, boolean quick) { try { boolean latin = VettingViewer.isLatinScriptLocale(sourceFile); Status status = new Status(); output.append("

Summary

\n") .append("

It is important that you read " + "" + "Priority Items before starting!

") .append("
\n") .append("\n") .append("" + "" + "" + "" + "\n"); // find the choice to check // OLD if !vetting and missing != 0, use missing. Otherwise pick first. Choice checkedItem = null; // if (nonVettingPhase && problemCounter.get(Choice.missingCoverage) != 0) { // checkedItem = Choice.missingCoverage; // } for (Choice choice : choices) { if (quick && choice != Choice.error && choice != Choice.warning) { //if "quick" mode, only show errors and warnings continue; } long count = outputFileInfo.problemCounter.get(choice); output.append("\n\t\n\t\n"); } output.append("
CountIssueDescription
") .append(nf.format(count)) .append("") .append(""); choice.appendDisplay("", output); output.append("") .append(choice.description) .append("
\n
\n" + ""); // gather information on choices on each page Relation, Choice> choicesForHeader = Relation.of( new HashMap, Set>(), HashSet.class); Relation, Choice> choicesForSection = Relation.of( new HashMap, Set>(), HashSet.class); for (Entry, Set> entry0 : sorted.keyValuesSet()) { SectionId section = entry0.getKey().get0(); PageId subsection = entry0.getKey().get1(); final Set rows = entry0.getValue(); for (WritingInfo pathInfo : rows) { String header = pathInfo.codeOutput.getHeader(); Set choicesForPath = pathInfo.problems; choicesForSection.putAll(Row.of(section, subsection), choicesForPath); choicesForHeader.putAll(Row.of(section, subsection, header), choicesForPath); } } final String localeId = sourceFile.getLocaleID(); final CLDRLocale locale = CLDRLocale.getInstance(localeId); int count = 0; for (Entry, Set> entry0 : sorted.keyValuesSet()) { SectionId section = entry0.getKey().get0(); PageId subsection = entry0.getKey().get1(); final Set rows = entry0.getValue(); rows.iterator().next(); // getUrl(localeId); (no side effect?) // http://kwanyin.unicode.org/cldr-apps/survey?_=ur&x=scripts // http://unicode.org/cldr-apps/survey?_=ur&x=scripts output.append("\n

Section: ") .append(section.toString()) .append(" — Page: ") .append(subsection.toString()) .append(" (" + rows.size() + ")

\n"); startTable(choicesForSection.get(Row.of(section, subsection)), output); String oldHeader = ""; for (WritingInfo pathInfo : rows) { String header = pathInfo.codeOutput.getHeader(); String code = pathInfo.codeOutput.getCode(); String path = pathInfo.codeOutput.getOriginalPath(); Set choicesForPath = pathInfo.problems; if (!header.equals(oldHeader)) { Set headerChoices = choicesForHeader.get(Row.of(section, subsection, header)); output.append("\n"); output.append(" "); output.append(header); output.append("\n\n"); oldHeader = header; } output.append("\n"); addCell(output, nf.format(++count), null, "tv-num", HTMLType.plain); // path addCell(output, code, null, "tv-code", HTMLType.plain); // English value if (choicesForPath.contains(Choice.englishChanged)) { String winning = englishFile.getWinningValue(path); String cellValue = winning == null ? "missing" : TransliteratorUtilities.toHTML .transform(winning); String previous = outdatedPaths.getPreviousEnglish(path); if (previous != null) { cellValue += "
OLD: " + TransliteratorUtilities.toHTML.transform(previous) + ""; } else { cellValue += "
missing"; } addCell(output, cellValue, null, "tv-eng", HTMLType.markup); } else { addCell(output, englishFile.getWinningValue(path), null, "tv-eng", HTMLType.plain); } // value for last version final String oldStringValue = lastSourceFile == null ? null : lastSourceFile.getWinningValue(path); MissingStatus oldValueMissing = getMissingStatus(lastSourceFile, path, status, latin); addCell(output, oldStringValue, null, oldValueMissing != MissingStatus.PRESENT ? "tv-miss" : "tv-last", HTMLType.plain); // value for last version String newWinningValue = sourceFile.getWinningValue(path); if (Objects.equals(newWinningValue, oldStringValue)) { newWinningValue = "="; } addCell(output, newWinningValue, null, choicesForPath.contains(Choice.missingCoverage) ? "tv-miss" : "tv-win", HTMLType.plain); // Fix? // http://unicode.org/cldr/apps/survey?_=az&xpath=%2F%2Fldml%2FlocaleDisplayNames%2Flanguages%2Flanguage%5B%40type%3D%22az%22%5D output.append(" "); Choice.appendDisplay(choicesForPath, "", output); // String otherUrl = pathInfo.getUrl(sourceFile.getLocaleID()); output.append(""); // if (!otherUrl.equals(url)) { // output.append("Section*"); // } if (!pathInfo.htmlMessage.isEmpty()) { addCell(output, pathInfo.htmlMessage, null, "tv-test", HTMLType.markup); } output.append("\n"); } output.append("\n"); } } catch (IOException e) { throw new ICUUncheckedIOException(e); // damn'ed checked exceptions } } /** * * @param output * @param choices * See the class description for more information. * @param localeId * @param user * @param usersLevel * @param nonVettingPhase */ public ArrayList getErrorOnPath(EnumSet choices, String localeID, T user, Level usersLevel, boolean nonVettingPhase, String path) { // Gather the relevant paths // each one will be marked with the choice that it triggered. Relation, WritingInfo> sorted = Relation.of( new TreeMap, Set>(), TreeSet.class); CLDRFile sourceFile = cldrFactory.make(localeID, true); // Initialize CLDRFile lastSourceFile = null; try { lastSourceFile = cldrFactoryOld.make(localeID, true); } catch (Exception e) { } EnumSet errors = new FileInfo().getFileInfo(sourceFile, lastSourceFile, sorted, choices, localeID, nonVettingPhase, user, usersLevel, false, path).problems; ArrayList out = new ArrayList(); for (Object error : errors.toArray()) { out.add(((Choice) error).buttonLabel); } return out; } /*private void getJSONReview(Appendable output, CLDRFile sourceFile, CLDRFile lastSourceFile, Relation, WritingInfo> sorted, EnumSet choices, String localeID, boolean nonVettingPhase, FileInfo outputFileInfo, boolean quick ) { try { boolean latin = VettingViewer.isLatinScriptLocale(sourceFile); JSONObject reviewInfo = new JSONObject(); JSONArray notificationsCount = new JSONArray(); List notifications = new ArrayList(); Status status = new Status(); for (Choice choice : choices) { notificationsCount.put(new JSONObject().put("name",choice.buttonLabel.replace(' ', '_')).put("description", choice.description).put("count", outputFileInfo.problemCounter.get(choice))); notifications.add(choice.buttonLabel); } reviewInfo.put("notification", notificationsCount); // gather information on choices on each page //output.append(reviewInfo.toString()); Relation, Choice> choicesForHeader = Relation.of( new HashMap, Set>(), HashSet.class); Relation, Choice> choicesForSection = Relation.of( new HashMap, Set>(), HashSet.class); Comparator> comparator = new Comparator>() { @Override public int compare(R4 o1, R4 o2) { int compChoice = o2.get0().order - o1.get0().order; if(compChoice == 0) { int compSection = o1.get1().compareTo(o2.get1()); if(compSection == 0) { int compPage = o1.get2().compareTo(o2.get2()); if(compPage == 0) return o1.get3().compareTo(o2.get3()); else return 0; } else return compSection; } else return compChoice; } }; Relation, WritingInfo> notificationsList = Relation.of( new TreeMap, Set>(comparator), TreeSet.class); //TODO we can prob do it in only one loop, but with that we can sort for (Entry, Set> entry0 : sorted.keyValuesSet()) { final Set rows = entry0.getValue(); for (WritingInfo pathInfo : rows) { Set choicesForPath = pathInfo.problems; SectionId section = entry0.getKey().get0(); PageId subsection = entry0.getKey().get1(); for(Choice choice : choicesForPath) { //reviewInfo notificationsList.put(Row.of(choice, section, subsection, pathInfo.codeOutput.getHeader()), pathInfo); } } } JSONArray allNotifications = new JSONArray(); for(Entry, Set> entry : notificationsList.keyValuesSet()) { String notificationName = entry.getKey().get0().buttonLabel.replace(' ', '_'); int notificationNumber = entry.getKey().get0().order; String sectionName = entry.getKey().get1().name(); String pageName = entry.getKey().get2().name(); String headerName = entry.getKey().get3(); if(allNotifications.optJSONObject(notificationNumber) == null) { allNotifications.put(notificationNumber,new JSONObject().put(notificationName, new JSONObject())); } JSONObject sections = allNotifications.getJSONObject(notificationNumber).getJSONObject(notificationName); if(sections.optJSONObject(sectionName) == null) { sections.accumulate(sectionName, new JSONObject()); } JSONObject pages = sections.getJSONObject(sectionName); if(pages.optJSONObject(pageName) == null) { pages.accumulate(pageName, new JSONObject()); } JSONObject header = pages.getJSONObject(pageName); JSONArray allContent = new JSONArray(); //real info for(WritingInfo info : entry.getValue()) { JSONObject content = new JSONObject(); String code = info.codeOutput.getCode(); String path = info.codeOutput.getOriginalPath(); Set choicesForPath = info.problems; //code content.put("code",code); content.put("path", ctx.sm.xpt.getByXpath(path)); //english if (choicesForPath.contains(Choice.englishChanged)) { String winning = englishFile.getWinningValue(path); String cellValue = winning == null ? "missing" : TransliteratorUtilities.toHTML .transform(winning); String previous = outdatedPaths.getPreviousEnglish(path); if (previous != null) { cellValue += "
OLD: " + TransliteratorUtilities.toHTML.transform(previous) + ""; } else { cellValue += "
missing"; } content.put("english", cellValue); } else { content.put("english",englishFile.getWinningValue(path)); } //old release final String oldStringValue = lastSourceFile == null ? null : lastSourceFile.getWinningValue(path); content.put("old", oldStringValue); // //winning value String newWinningValue = sourceFile.getWinningValue(path); if (CharSequences.equals(newWinningValue, oldStringValue)) { newWinningValue = "="; } content.put("winning",newWinningValue); //comment String comment = ""; if (!info.htmlMessage.isEmpty()) { comment = info.htmlMessage; } content.put("comment", comment.replace("\"", """)); content.put("id", StringId.getHexId(info.codeOutput.getOriginalPath())); allContent.put(content); } header.put(headerName, allContent); } reviewInfo.put("allNotifications", allNotifications); //hidden info ReviewHide review = new ReviewHide(); reviewInfo.put("hidden", review.getHiddenField(ctx.userId(), ctx.getLocale().toString())); reviewInfo.put("direction", ctx.getDirectionForLocale()); output.append(reviewInfo.toString()); } catch (JSONException | IOException e) { e.printStackTrace(); } } */ private void startTable(Set choices, Appendable output) throws IOException { output.append("\n"); output.append("" + "" + "" + "" + "" + "" + "" + "" + "\n"); } enum HTMLType { plain, markup } private void addCell(Appendable output, String value, String title, String classValue, HTMLType htmlType) throws IOException { output.append(" "); } else { if (title != null && !title.equals(value)) { output.append("title='").append(TransliteratorUtilities.toHTML.transform(title)).append('\''); } output .append("'>") .append(htmlType == HTMLType.markup ? value : TransliteratorUtilities.toHTML.transform(value)) .append("\n"); } } /** * Find the status of the items in the file. * @param file the source. Must be a resolved file, made with minimalDraftStatus = unconfirmed * @param pathHeaderFactory PathHeaderFactory. * @param foundCounter output counter of the number of paths with values having contributed or approved status * @param unconfirmedCounter output counter of the number of paths with values, but neither contributed nor approved status * @param missingCounter output counter of the number of paths without values * @param missingPaths output if not null, the specific paths that are missing. * @param unconfirmedPaths TODO */ public static void getStatus(CLDRFile file, PathHeader.Factory pathHeaderFactory, Counter foundCounter, Counter unconfirmedCounter, Counter missingCounter, Relation missingPaths, Set unconfirmedPaths) { getStatus(file.fullIterable(), file, pathHeaderFactory, foundCounter, unconfirmedCounter, missingCounter, missingPaths, unconfirmedPaths); } /** * Find the status of the items in the file. * @param allPaths manual list of paths * @param file the source. Must be a resolved file, made with minimalDraftStatus = unconfirmed * @param pathHeaderFactory PathHeaderFactory. * @param foundCounter output counter of the number of paths with values having contributed or approved status * @param unconfirmedCounter output counter of the number of paths with values, but neither contributed nor approved status * @param missingCounter output counter of the number of paths without values * @param missingPaths output if not null, the specific paths that are missing. * @param unconfirmedPaths TODO */ public static void getStatus(Iterable allPaths, CLDRFile file, PathHeader.Factory pathHeaderFactory, Counter foundCounter, Counter unconfirmedCounter, Counter missingCounter, Relation missingPaths, Set unconfirmedPaths) { if (!file.isResolved()) { throw new IllegalArgumentException("File must be resolved, no minimal draft status"); } foundCounter.clear(); unconfirmedCounter.clear(); missingCounter.clear(); Status status = new Status(); boolean latin = VettingViewer.isLatinScriptLocale(file); CoverageLevel2 coverageLevel2 = CoverageLevel2.getInstance(file.getLocaleID()); for (String path : allPaths) { PathHeader ph = pathHeaderFactory.fromPath(path); if (ph.getSectionId() == SectionId.Special) { continue; } Level level = coverageLevel2.getLevel(path); // String localeFound = file.getSourceLocaleID(path, status); // String value = file.getSourceLocaleID(path, status); MissingStatus missingStatus = VettingViewer.getMissingStatus(file, path, status, latin); switch (missingStatus) { case ABSENT: missingCounter.add(level, 1); if (missingPaths != null && level.compareTo(Level.MODERN) <= 0) { missingPaths.put(missingStatus, path); } break; case ALIASED: case PRESENT: String fullPath = file.getFullXPath(path); if (fullPath.contains("unconfirmed") || fullPath.contains("provisional")) { unconfirmedCounter.add(level, 1); if (unconfirmedPaths != null && level.compareTo(Level.MODERN) <= 0) { unconfirmedPaths.add(path); } } else { foundCounter.add(level, 1); } break; case MISSING_OK: case ROOT_OK: break; default: throw new IllegalArgumentException(); } } } /** * Simple example of usage * * @param args * @throws IOException */ final static Options myOptions = new Options(); enum MyOptions { repeat(null, null, "Repeat indefinitely"), filter(".*", ".*", "Filter files"), locale(".*", "af", "Single locale for testing"), source(".*", CLDRPaths.MAIN_DIRECTORY, // CldrUtility.TMP2_DIRECTORY + "/vxml/common/main" "if summary, creates filtered version (eg -d main): does a find in the name, which is of the form dir/file"), verbose(null, null, "verbose debugging messages"), output(".*", CLDRPaths.GEN_DIRECTORY + "vetting/", "filter the raw files (non-summary, mostly for debugging)"),; // boilerplate final Option option; MyOptions(String argumentPattern, String defaultArgument, String helpText) { option = myOptions.add(this, argumentPattern, defaultArgument, helpText); } } public static void main(String[] args) throws IOException { SHOW_SUBTYPES = true; myOptions.parse(MyOptions.source, args, true); boolean repeat = MyOptions.repeat.option.doesOccur(); String fileFilter = MyOptions.filter.option.getValue(); String myOutputDir = repeat ? null : MyOptions.output.option.getValue(); String LOCALE = MyOptions.locale.option.getValue(); String CURRENT_MAIN = MyOptions.source.option.getValue(); final String version = ToolConstants.PREVIOUS_CHART_VERSION; final String lastMain = CLDRPaths.ARCHIVE_DIRECTORY + "/cldr-" + version + "/common/main"; //final String lastMain = CLDRPaths.ARCHIVE_DIRECTORY + "/common/main"; do { Timer timer = new Timer(); timer.start(); Factory cldrFactory = Factory.make(CURRENT_MAIN, fileFilter); cldrFactory.setSupplementalDirectory(new File(CLDRPaths.SUPPLEMENTAL_DIRECTORY)); Factory cldrFactoryOld = Factory.make(lastMain, fileFilter); SupplementalDataInfo supplementalDataInfo = SupplementalDataInfo .getInstance(CLDRPaths.SUPPLEMENTAL_DIRECTORY); CheckCLDR.setDisplayInformation(cldrFactory.make("en", true)); // FAKE this, because we don't have access to ST data UsersChoice usersChoice = new UsersChoice() { // Fake values for now public String getWinningValueForUsersOrganization(CLDRFile cldrFile, String path, Organization user) { if (path.contains("USD")) { return "&dummy ‘losing’ value"; } return null; // assume we didn't vote on anything else. } // Fake values for now public VoteStatus getStatusForUsersOrganization(CLDRFile cldrFile, String path, Organization user) { String usersValue = getWinningValueForUsersOrganization(cldrFile, path, user); String winningValue = cldrFile.getWinningValue(path); if (usersValue != null && !Objects.equals(usersValue, winningValue)) { return VoteStatus.losing; } String fullPath = cldrFile.getFullXPath(path); if (fullPath.contains("AMD") || fullPath.contains("unconfirmed") || fullPath.contains("provisional")) { return VoteStatus.provisionalOrWorse; } else if (fullPath.contains("AED")) { return VoteStatus.disputed; } else if (fullPath.contains("AED")) { return VoteStatus.ok_novotes; } return VoteStatus.ok; } }; // create the tableView and set the options desired. // The Options should come from a GUI; from each you can get a long // description and a button label. // Assuming user can be identified by an int VettingViewer tableView = new VettingViewer(supplementalDataInfo, cldrFactory, cldrFactoryOld, usersChoice, "CLDR " + version, "Winning Proposed"); // here are per-view parameters final EnumSet choiceSet = EnumSet.allOf(Choice.class); String localeStringID = LOCALE; int userNumericID = 666; Level usersLevel = Level.MODERN; // http: // unicode.org/cldr-apps/survey?_=ur if (!repeat) { FileCopier.ensureDirectoryExists(myOutputDir); FileCopier.copy(VettingViewer.class, "vettingView.css", myOutputDir); FileCopier.copy(VettingViewer.class, "vettingView.js", myOutputDir); } System.out.println("Creation: " + timer.getDuration() / NANOSECS + " secs"); // timer.start(); // writeFile(tableView, choiceSet, "", localeStringID, userNumericID, usersLevel, CodeChoice.oldCode); // System.out.println(timer.getDuration() / NANOSECS + " secs"); timer.start(); writeFile(myOutputDir, tableView, choiceSet, "", localeStringID, userNumericID, usersLevel, CodeChoice.newCode, null); System.out.println("Code: " + timer.getDuration() / NANOSECS + " secs"); timer.start(); writeFile(myOutputDir, tableView, choiceSet, "", localeStringID, userNumericID, usersLevel, CodeChoice.summary, Organization.google); System.out.println("Summary: " + timer.getDuration() / NANOSECS + " secs"); // timer.start(); // writeFile(tableView, choiceSet, "", localeStringID, userNumericID, usersLevel, CodeChoice.summary, // Organization.ibm); // System.out.println(timer.getDuration() / NANOSECS + " secs"); // // check that the choices work. // for (Choice choice : choiceSet) { // timer.start(); // writeFile(tableView, EnumSet.of(choice), "-" + choice.abbreviation, localeStringID, userNumericID, // usersLevel); // System.out.println(timer.getDuration() / NANOSECS + " secs"); // } } while (repeat); } public enum CodeChoice { /** For the normal (locale) view of data **/ newCode, // /** @deprecated **/ // oldCode, /** For a summary view of data **/ summary } public static void writeFile(String myOutputDir, VettingViewer tableView, final EnumSet choiceSet, String name, String localeStringID, int userNumericID, Level usersLevel, CodeChoice newCode, Organization organization) throws IOException { // open up a file, and output some of the styles to control the table // appearance PrintWriter out = myOutputDir == null ? new PrintWriter(new StringWriter()) : FileUtilities.openUTF8Writer(myOutputDir, "vettingView" + name + (newCode == CodeChoice.newCode ? "" : newCode == CodeChoice.summary ? "-summary" : "") + (organization == null ? "" : "-" + organization.toString()) + ".html"); // FileUtilities.appendFile(VettingViewer.class, "vettingViewerHead.txt", out); FileCopier.copy(VettingViewer.class, "vettingViewerHead.txt", out); out.append(getHeaderStyles()); out.append("\n"); out.println( "

Note: this is just a sample run. The user, locale, user's coverage level, and choices of tests will change the output. In a real ST page using these, the first three would " + "come from context, and the choices of tests would be set with radio buttons. Demo settings are:

\n
    " + "
  1. choices: " + choiceSet + "
  2. localeStringID: " + localeStringID + "
  3. userNumericID: " + userNumericID + "
  4. usersLevel: " + usersLevel + "
" + "

Notes: This is a static version, using old values and faked values (L) just for testing." + (TESTING ? "Also, the white cell after the Fix column is just for testing." : "") + "


\n"); // now generate the table with the desired options // The options should come from a GUI; from each you can get a long // description and a button label. // Assuming user can be identified by an int switch (newCode) { case newCode: tableView.generateHtmlErrorTables(out, choiceSet, localeStringID, organization, usersLevel, SHOW_ALL, false); break; // case oldCode: // tableView.generateHtmlErrorTablesOld(out, choiceSet, localeStringID, userNumericID, usersLevel, SHOW_ALL); // break; case summary: //System.out.println(tableView.getName("zh_Hant_HK")); tableView.generateSummaryHtmlErrorTables(out, choiceSet, null, organization); break; } out.println("\n\n"); out.close(); } }
No.CodeEnglish" + lastVersionTitle + "" + currentWinningTitle + "Fix?Comment
missing