// Ensures that embed bound properties are writable
  private void checkEmbedAgainst(
      PageCompilingContext pc,
      EvaluatorCompiler compiler,
      Map<String, String> properties,
      Class<?> embedClass,
      Node node) {

    // TODO also type check them against expressions
    for (String property : properties.keySet()) {
      try {
        if (!compiler.isWritable(property)) {
          pc.errors.add(
              CompileError.in(node.outerHtml())
                  // TODO we need better line number detection if there is whitespace between the
                  // annotation and tag.
                  .near(node.siblingIndex() - 1) // TODO -  line number of the annotation
                  .causedBy(
                      CompileErrors.PROPERTY_NOT_WRITEABLE,
                      String.format(
                          "Property %s#%s was not writable. Did you forget to create "
                              + "a setter or @Visible annotation?",
                          embedClass.getSimpleName(), property)));
        }
      } catch (ExpressionCompileException ece) {
        pc.errors.add(
            CompileError.in(node.outerHtml())
                .near(node.siblingIndex()) // TODO - line number
                .causedBy(CompileErrors.ERROR_COMPILING_PROPERTY));
      }
    }
  }
  private Map<String, Type> parseRepeatScope(PageCompilingContext pc, String[] extract, Node node) {
    RepeatToken repeat = registry.parseRepeat(extract[1]);
    Map<String, Type> context = Maps.newHashMap();

    // Verify that @Repeat was parsed correctly.
    if (null == repeat.var()) {
      pc.errors.add(
          CompileError.in(node.outerHtml())
              .near(node.siblingIndex()) // TODO - line number
              .causedBy(CompileErrors.MISSING_REPEAT_VAR));
    }
    if (null == repeat.items()) {
      pc.errors.add(
          CompileError.in(node.outerHtml())
              .near(node.siblingIndex()) // TODO  - line number
              .causedBy(CompileErrors.MISSING_REPEAT_ITEMS));
    }

    try {
      Type egressType = pc.lexicalScopes.peek().resolveEgressType(repeat.items());

      // convert to collection if we need to
      Type elementType;
      Class<?> egressClass = Generics.erase(egressType);
      if (egressClass.isArray()) {
        elementType = Generics.getArrayComponentType(egressType);
      } else if (Collection.class.isAssignableFrom(egressClass)) {
        elementType =
            Generics.getTypeParameter(egressType, Collection.class.getTypeParameters()[0]);
      } else {
        pc.errors.add(
            CompileError.in(node.outerHtml())
                .near(node.siblingIndex()) // TODO - line number
                .causedBy(CompileErrors.REPEAT_OVER_ATOM));
        return Collections.emptyMap();
      }

      context.put(repeat.var(), elementType);
      context.put(repeat.pageVar(), pc.page);
      context.put("__page", pc.page);
      context.put("index", int.class);
      context.put("isLast", boolean.class);

    } catch (ExpressionCompileException e) {
      pc.errors.add(
          CompileError.in(node.outerHtml())
              .near(node.siblingIndex()) // TODO - line number
              .causedBy(e));
    }

    return context;
  }
  private void checkFormFields(PageCompilingContext pc, Node element) {
    if (null == pc.form) return;

    String action = pc.form.attr("action");

    // Only look at contextual uris (i.e. hosted by us).
    // TODO - relative, not starting with '/'
    if (null == action || (!action.startsWith("/"))) return;

    final PageBook.Page page = pageBook.get(action);

    // Only look at pages we actually have registered.
    if (null == page) {
      pc.warnings.add(
          CompileError.in(element.outerHtml())
              .near(line(element))
              .causedBy(CompileErrors.UNRESOLVABLE_FORM_ACTION));

      return;
    }

    // If we're inside a form do a throw-away compile against the target page.
    if ("input".equals(element.nodeName()) || "textarea".equals(element.nodeName())) {
      String name = element.attr("name");

      // Skip submits and buttons.
      if (skippable(element.attr("type"))) return;

      // TODO Skip empty?
      if (null == name) {
        pc.warnings.add(
            CompileError.in(element.outerHtml())
                .near(line(element))
                .causedBy(CompileErrors.FORM_MISSING_NAME));

        return;
      }

      // Compile expression path.
      try {
        new MvelEvaluatorCompiler(page.pageClass()).compile(name);

      } catch (ExpressionCompileException e) {
        // TODO Very hacky, needed to strip out xmlns attribution.
        pc.warnings.add(
            CompileError.in(element.outerHtml())
                .near(element.siblingIndex()) // TODO - line number
                .causedBy(CompileErrors.UNRESOLVABLE_FORM_BINDING, e));
      }
    }
  }
Example #4
0
 private static CompileError makeCompileError(
     AstElement e, String msg, ErrorHandler handler, CompileError.ErrorType errorType)
     throws CompileError {
   WPos pos = e.attrErrorPos();
   if (handler.isUnitTestMode()) {
     throw new CompileError(pos, msg);
   }
   ListIterator<CompileError> it = handler.getErrors().listIterator();
   while (it.hasNext()) {
     CompileError err = it.next();
     if (err.getSource().getFile().equals(pos.getFile())) {
       if (bigger(err.getSource(), pos)) {
         // remove bigger errors
         it.remove();
       } else if (bigger(pos, err.getSource()) || equal(pos, err.getSource())) {
         // do not add smaller or equal errors
         return null;
       }
     }
   }
   return new CompileError(pos, msg, errorType);
 }
  private void checkUriConsistency(PageCompilingContext pc, Node element) {
    String uriAttrib = element.attr("action");
    if (null == uriAttrib) uriAttrib = element.attr("src");
    if (null == uriAttrib) uriAttrib = element.attr("href");

    if (null != uriAttrib) {

      // Verify that such a uri exists in the page book,
      // only if it is contextual--ignore abs & relative URIs.
      if (uriAttrib.startsWith("/"))
        if (null == pageBook.nonCompilingGet(uriAttrib))
          pc.warnings.add(
              CompileError.in(element.outerHtml())
                  .near(element.siblingIndex()) // TODO - line number
                  .causedBy(CompileErrors.UNRESOLVABLE_FORM_ACTION, uriAttrib));
    }
  }
  /**
   * This method converts an XML element into a specific kind of widget. Special cases are the XML
   * widget, Header, @Require widget. Otherwise a standard widget is created.
   */
  @SuppressWarnings({"JavaDoc"})
  @NotNull
  private <N extends Node> Renderable widgetize(
      PageCompilingContext pc, N node, WidgetChain childsChildren) {
    if (node instanceof XmlDeclaration) {
      try {
        XmlDeclaration decl = (XmlDeclaration) node;
        return registry.xmlDirectiveWidget(decl.getWholeDeclaration(), pc.lexicalScopes.peek());
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
      }
    }

    // Header widget is a special case, where we match by the name of the tag =(
    if ("head".equals(node.nodeName())) {
      try {
        return registry.headWidget(
            childsChildren, parseAttribs(node.attributes()), pc.lexicalScopes.peek());
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
      }
    }

    String annotation = node.attr(ANNOTATION);

    // if there is no annotation, treat as a raw xml-widget (i.e. tag)
    if ((null == annotation) || 0 == annotation.trim().length())
      try {
        checkUriConsistency(pc, node);
        checkFormFields(pc, node);

        return registry.xmlWidget(
            childsChildren,
            node.nodeName(),
            parseAttribs(node.attributes()),
            pc.lexicalScopes.peek());
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));

        return Chains.terminal();
      }

    // Special case: is this annotated with @Require
    //   if so, tags in head need to be promoted to head of enclosing page.
    if (REQUIRE_WIDGET.equalsIgnoreCase(annotation.trim()))
      try {
        return registry.requireWidget(
            registry.xmlWidget(
                childsChildren,
                node.nodeName(),
                parseAttribs(node.attributes()),
                pc.lexicalScopes.peek()));
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));

        return Chains.terminal();
      }

    // If this is NOT a self-rendering widget, give it a child.
    // final String widgetName = node.attr(ANNOTATION_KEY).trim().toLowerCase());
    final String widgetName = node.attr(ANNOTATION_KEY).toLowerCase();

    if (!registry.isSelfRendering(widgetName))
      try {
        childsChildren =
            Chains.singleton(
                registry.xmlWidget(
                    childsChildren,
                    node.nodeName(),
                    parseAttribs(node.attributes()),
                    pc.lexicalScopes.peek()));
      } catch (ExpressionCompileException e) {
        pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
      }

    // Recursively build widget from [Key, expression, child widgets].
    try {
      return registry.newWidget(
          widgetName, node.attr(ANNOTATION_CONTENT), childsChildren, pc.lexicalScopes.peek());
    } catch (ExpressionCompileException e) {
      pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));

      // This should never be used.
      return Chains.terminal();
    }
  }
  /** Walks the DOM recursively, and converts elements into corresponding sitebricks widgets. */
  @NotNull
  private <N extends Node> WidgetChain walk(PageCompilingContext pc, N node) {
    WidgetChain widgetChain = Chains.proceeding();
    for (Node n : node.childNodes()) {
      if (n instanceof Element) {
        final Element child = (Element) n;

        // push form if this is a form tag
        if (child.tagName().equals("form")) pc.form = (Element) n;

        // setup a lexical scope if we're going into a repeat widget (by reading the previous node)
        final boolean shouldPopScope = lexicalClimb(pc, child);

        // continue recursing down, perform a post-order, depth-first traversal of the DOM
        WidgetChain childsChildren;
        try {
          childsChildren = walk(pc, child);

          // process the widget itself into a Renderable with child tree
          widgetChain.addWidget(widgetize(pc, child, childsChildren));
        } finally {
          lexicalDescend(pc, child, shouldPopScope);
        }

      } else if (n instanceof TextNode) {
        TextNode child = (TextNode) n;
        Renderable textWidget;

        // setup a lexical scope if we're going into a repeat widget (by reading the previous node)
        final boolean shouldPopScope = lexicalClimb(pc, child);

        // construct the text widget
        try {
          textWidget = registry.textWidget(cleanHtml(n), pc.lexicalScopes.peek());

          // if there are no annotations, add the text widget to the chain
          if (!child.hasAttr(ANNOTATION_KEY)) {
            widgetChain.addWidget(textWidget);
          } else {
            // construct a new widget chain for this text node
            WidgetChain childsChildren = Chains.proceeding().addWidget(textWidget);

            // make a new widget for the annotation, making the text chain the child
            String widgetName = child.attr(ANNOTATION_KEY).toLowerCase();
            Renderable annotationWidget =
                registry.newWidget(
                    widgetName,
                    child.attr(ANNOTATION_CONTENT),
                    childsChildren,
                    pc.lexicalScopes.peek());
            widgetChain.addWidget(annotationWidget);
          }

        } catch (ExpressionCompileException e) {
          pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
        }

        if (shouldPopScope) pc.lexicalScopes.pop();

      } else if ((n instanceof Comment) || (n instanceof DataNode)) {
        // process as raw text widget
        try {
          widgetChain.addWidget(registry.textWidget(cleanHtml(n), pc.lexicalScopes.peek()));
        } catch (ExpressionCompileException e) {

          pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
        }
      } else if (n instanceof XmlDeclaration) {
        try {
          widgetChain.addWidget(
              registry.xmlDirectiveWidget(
                  ((XmlDeclaration) n).getWholeDeclaration(), pc.lexicalScopes.peek()));
        } catch (ExpressionCompileException e) {
          pc.errors.add(CompileError.in(node.outerHtml()).near(line(node)).causedBy(e));
        }
      }
    }

    // return computed chain, or a terminal
    return widgetChain;
  }