/**
   * @param containingHtmlElement the name of the HTML element containing el. If the HTML element is
   *     contained inside a template construct then this name may differ from el's immediate parent.
   */
  private void inspectElement(JobEnvelope source, Element el, ElKey containingHtmlElement) {
    ElKey elKey = ElKey.forElement(el);

    // Recurse early so that ihtml:dynamic elements have been parsed before we
    // process the attributes element list.
    for (Node child : Nodes.childrenOf(el)) {
      inspect(source, child, elKey);
    }

    // For each attribute allowed on this element type, ensure that
    // (1) If it is not specified, and its default value is not allowed, then
    //     it is added with a known safe value.
    // (2) Its value is rewritten as appropriate.
    // We don't have to worry about disallowed attributes since those will
    // not be present in scriptsPerNode.  The TemplateSanitizer should have
    // stripped those out.  The TemplateSanitizer should also have stripped out
    // disallowed elements.
    if (!htmlSchema.isElementAllowed(elKey)) {
      return;
    }

    HTML.Element elInfo = htmlSchema.lookupElement(elKey);
    List<HTML.Attribute> attrs = elInfo.getAttributes();
    if (attrs != null) {
      for (HTML.Attribute a : attrs) {
        AttribKey attrKey = a.getKey();
        if (!htmlSchema.isAttributeAllowed(attrKey)) {
          continue;
        }
        Attr attr = null;
        String aUri = attrKey.ns.uri;
        String aName = attrKey.localName;
        Attr unsafe = el.getAttributeNodeNS(aUri, aName);
        if (unsafe != null && a.getValueCriterion().accept(unsafe.getValue())) {
          attr = unsafe;
        } else if ((a.getDefaultValue() != null
                && !a.getValueCriterion().accept(a.getDefaultValue()))
            || !a.isOptional()) {
          attr = el.getOwnerDocument().createAttributeNS(aUri, aName);
          String safeValue;
          if (a.getType() == HTML.Attribute.Type.URI) {
            safeValue = "" + Nodes.getFilePositionFor(el).source().getUri();
          } else {
            safeValue = a.getSafeValue();
          }
          if (safeValue == null) {
            mq.addMessage(
                IhtmlMessageType.MISSING_ATTRIB, Nodes.getFilePositionFor(el), elKey, attrKey);
            continue;
          }
          attr.setNodeValue(safeValue);
          el.setAttributeNodeNS(attr);
        }
        if (attr != null) {
          inspectHtmlAttribute(source, attr, a);
        }
      }
    }
    scriptsPerNode.put(el, null);
  }
 private void inspectFragment(
     JobEnvelope source, DocumentFragment f, ElKey containingHtmlElement) {
   scriptsPerNode.put(f, null);
   for (Node child : Nodes.childrenOf(f)) {
     // We know that top level text nodes in a document fragment
     // are not significant if they are just newlines and indentation.
     // This decreases output size significantly.
     if (isWhitespaceOnlyTextNode(child)) {
       continue;
     }
     inspect(source, child, containingHtmlElement);
   }
 }
  private void runTest(String goldenIhtml, String inputIhtml, Message... expectedMessages)
      throws Exception {
    Element ihtmlRoot =
        new DomParser(
                DomParser.makeTokenQueue(
                    FilePosition.startOfFile(is), new StringReader(inputIhtml), true, false),
                true,
                mq)
            .parseDocument();
    new IhtmlSanityChecker(mq).check(ihtmlRoot);

    for (Message msg : expectedMessages) {
      assertMessage(
          true,
          msg.getMessageType(),
          msg.getMessageLevel(),
          msg.getMessageParts().toArray(new MessagePart[0]));
    }
    assertMessagesLessSevereThan(MessageLevel.WARNING);

    String checkedIhtml = Nodes.render(ihtmlRoot, MarkupRenderMode.XML);
    assertEquals(goldenIhtml, checkedIhtml);
  }