// 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)); } } }
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; }