All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gxp.compiler.i18ncheck..svn.text-base.I18nChecker.svn-base Maven / Gradle / Ivy

Go to download

Google XML Pages (GXP) is a templating system used to generate XML/SGML markup (most often HTML).

The newest version!
/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.gxp.compiler.i18ncheck;

import com.google.common.base.Preconditions;
import com.google.gxp.compiler.alerts.AlertSetBuilder;
import com.google.gxp.compiler.alerts.AlertSink;
import com.google.gxp.compiler.base.AbbrExpression;
import com.google.gxp.compiler.base.AttrBundleParam;
import com.google.gxp.compiler.base.BoundCall;
import com.google.gxp.compiler.base.Call;
import com.google.gxp.compiler.base.CallVisitor;
import com.google.gxp.compiler.base.Callable;
import com.google.gxp.compiler.base.ContentType;
import com.google.gxp.compiler.base.DefaultingTypeVisitor;
import com.google.gxp.compiler.base.ExhaustiveExpressionVisitor;
import com.google.gxp.compiler.base.Expression;
import com.google.gxp.compiler.base.NoMessage;
import com.google.gxp.compiler.base.OutputElement;
import com.google.gxp.compiler.base.Parameter;
import com.google.gxp.compiler.base.PlaceholderEnd;
import com.google.gxp.compiler.base.PlaceholderStart;
import com.google.gxp.compiler.base.StringConstant;
import com.google.gxp.compiler.base.Template;
import com.google.gxp.compiler.base.Type;
import com.google.gxp.compiler.base.TypeVisitor;
import com.google.gxp.compiler.base.UnboundCall;
import com.google.gxp.compiler.base.UnexpectedNodeException;
import com.google.gxp.compiler.base.UnextractedMessage;
import com.google.gxp.compiler.base.ValidatedCall;
import com.google.gxp.compiler.collapse.SpaceCollapsedTree;
import com.google.gxp.compiler.phpivot.PlaceholderPivotedTree;
import com.google.gxp.compiler.reparent.Attribute;
import com.google.gxp.compiler.schema.AttributeValidator;
import com.google.gxp.compiler.schema.ContentFamily;
import com.google.gxp.compiler.schema.ElementValidator;
import com.google.gxp.compiler.schema.Schema;

import java.util.*;
import java.util.regex.Pattern;

/**
 * Examines a {@code SpaceCollapsedTree} and reports {@code
 * UnextractedContentAlert}s as appropriate.
 */
public class I18nChecker {
  private I18nChecker() {}

  public static final I18nChecker INSTANCE = new I18nChecker();

  public I18nCheckedTree apply(SpaceCollapsedTree tree, PlaceholderPivotedTree pivotedTree) {
    AlertSetBuilder alertSetBuilder = new AlertSetBuilder(pivotedTree.getAlerts());
    tree.getRoot().acceptVisitor(new Visitor(alertSetBuilder));

    return new I18nCheckedTree(pivotedTree.getSourcePosition(),
                               alertSetBuilder.buildAndClear(),
                               pivotedTree.getRoot());
  }

  /**
   * Visits a space collapsed tree and generates
   * {@code UnextractedContentAlert} instances where appropriate.
   */
  private static class Visitor
      extends ExhaustiveExpressionVisitor implements CallVisitor {
    private final AlertSink alertSink;
    private boolean visible = true;
    private boolean insideMsg = false;
    private boolean insidePh = false;
    private boolean insideNoMsg = false;

    Visitor(AlertSink alertSink) {
      this.alertSink = Preconditions.checkNotNull(alertSink);
    }

    @Override
    public Template visitTemplate(Template template) {
      boolean oldVisible = visible;

      // We want to examine default values, but we intentionally skip the
      // comments.
      for (Parameter parameter : template.getParameters()) {
        visible = isTypeVisible(parameter.getType());
        Expression defaultValue = parameter.getDefaultValue();
        if (defaultValue != null) {
          defaultValue.acceptVisitor(this);
        }
        visible = oldVisible;
      }

      // TODO(laurence): should probably move this check into
      // visitStringConstant instead, but we'll need access to the unescaped
      // form of the StringConstant.
      visible = isSchemaVisible(template.getSchema());
      template.getContent().acceptVisitor(this);
      visible = oldVisible;
      return template;
    }

    // \xa0 is non-breaking-space (nbsp)
    private static final Pattern LOCALE_INDEPENDENT_PATTERN =
        Pattern.compile("[\\s\\xa0]*");

    /**
     * Return true if and only if the specified string does not need to be
     * translated.
     */
    private static boolean isLocaleIndependent(String s) {
      return LOCALE_INDEPENDENT_PATTERN.matcher(s).matches();
    }

    @Override
    public Expression visitStringConstant(StringConstant node) {
      if (visible && (!insideMsg || insidePh) && !insideNoMsg
          && !isLocaleIndependent(node.evaluate())) {
        alertSink.add(new UnextractableContentAlert(node.getSourcePosition(),
                                                    node.getDisplayName()));
      }
      return node;
    }

    @Override
    public Expression visitAbbrExpression(AbbrExpression abbr) {
      boolean oldVisible = visible;
      visible = isTypeVisible(abbr.getType());
      apply(abbr.getValue());
      visible = oldVisible;

      apply(abbr.getContent());
      return abbr;
    }

    @Override
    public Expression visitCall(Call call) {
      return call.acceptCallVisitor(this);
    }

    @Override
    public Expression visitBoundCall(BoundCall call) {
      boolean oldVisible = visible;
      Callable callee = call.getCallee();
      for (Map.Entry param : call.getAttributes().entrySet()) {
        Type type = callee.getParameterByPrimary(param.getKey()).getType();
        visible = isTypeVisible(type);
        visitAttribute(param.getValue());
      }
      visible = oldVisible;
      return call;
    }

    @Override
    public Expression visitUnboundCall(UnboundCall call) {
      throw new UnexpectedNodeException(call);
    }

    @Override
    public Expression visitValidatedCall(ValidatedCall call) {
      throw new UnexpectedNodeException(call);
    }

    @Override
    public Expression visitOutputElement(OutputElement element) {
      boolean oldVisible = visible;
      ElementValidator elementValidator = element.getValidator();
      boolean oldInsideMsg = insideMsg;
      insideMsg = false;
      for (Attribute attr : element.getAttributes()) {
        AttributeValidator attrValidator =
            elementValidator.getAttributeValidator(attr.getName());
        visible = attrValidator.isFlagSet(AttributeValidator.Flag.VISIBLETEXT);
        visitAttribute(attr);
        visible = oldVisible;
      }
      insideMsg = oldInsideMsg;
      visible &= !elementValidator.isFlagSet(ElementValidator.Flag.INVISIBLEBODY);
      apply(element.getContent());
      visible = oldVisible;
      return element;
    }

    @Override
    public Expression visitAttrBundleParam(AttrBundleParam bundle) {
      boolean oldVisible = visible;
      boolean oldInsideMsg = insideMsg;
      insideMsg = false;
      for (Map.Entry entry : bundle.getAttrs().entrySet()) {
        AttributeValidator attrValidator = entry.getKey();
        visible = attrValidator.isFlagSet(AttributeValidator.Flag.VISIBLETEXT);
        visitAttribute(entry.getValue());
      }
      visible = oldVisible;
      insideMsg = oldInsideMsg;
      return bundle;
    }

    @Override
    public Expression visitUnextractedMessage(UnextractedMessage msg) {
      boolean oldInsideMsg = insideMsg;
      boolean oldInsidePh = insidePh;
      insideMsg = true;
      insidePh = false;
      super.visitUnextractedMessage(msg);
      insideMsg = oldInsideMsg;
      insidePh = oldInsidePh;
      return msg;
    }

    @Override
    public Expression visitNoMessage(NoMessage noMsg) {
      if (!visible) {
        // TODO(harryh): it would be nice to turn this into a BadNodePlacementError
        //               someday
        alertSink.add(new UnnecessaryNomsgWarning(noMsg));
      }
      insideNoMsg = true;
      super.visitNoMessage(noMsg);
      insideNoMsg = false;
      return noMsg;
    }

    @Override
    public Expression visitPlaceholderStart(PlaceholderStart phStart) {
      insidePh = true;
      return super.visitPlaceholderStart(phStart);
    }

    @Override
    public Expression visitPlaceholderEnd(PlaceholderEnd phEnd) {
      insidePh = false;
      return super.visitPlaceholderEnd(phEnd);
    }

    private static boolean isSchemaVisible(Schema schema) {
      return (schema.getContentFamily() == ContentFamily.MARKUP ||
              schema.getContentFamily() == ContentFamily.PLAINTEXT);
    }

    private static boolean isTypeVisible(Type type) {
      return type.acceptTypeVisitor(TYPE_VISITOR);
    }

    /**
     * A {@code TypeVisitor} that indicates that i18n checking should be done
     * for all types except for non markup content-types. Used for call
     * attributes, and default values of {@code Parameter}s.
     */
    private static final TypeVisitor TYPE_VISITOR = new DefaultingTypeVisitor() {
      public Boolean defaultVisitType(Type type) {
        return true;
      }

      public Boolean visitContentType(ContentType type) {
        return isSchemaVisible(type.getSchema());
      }
    };
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy