/**
   * WhiteboardObjectTextJabberImpl constructor.
   *
   * @param xml the XML string object to parse.
   */
  public WhiteboardObjectTextJabberImpl(String xml) {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder;
    try {
      builder = factory.newDocumentBuilder();
      InputStream in = new ByteArrayInputStream(xml.getBytes());
      Document doc = builder.parse(in);

      Element e = doc.getDocumentElement();
      String elementName = e.getNodeName();
      if (elementName.equals("text")) {
        // we have a text
        String id = e.getAttribute("id");
        double x = Double.parseDouble(e.getAttribute("x"));
        double y = Double.parseDouble(e.getAttribute("y"));
        String fill = e.getAttribute("fill");
        String fontFamily = e.getAttribute("font-family");
        int fontSize = Integer.parseInt(e.getAttribute("font-size"));
        String text = e.getTextContent();

        this.setID(id);
        this.setWhiteboardPoint(new WhiteboardPoint(x, y));
        this.setFontName(fontFamily);
        this.setFontSize(fontSize);
        this.setText(text);
        this.setColor(Color.decode(fill).getRGB());
      }
    } catch (ParserConfigurationException ex) {
      if (logger.isDebugEnabled()) logger.debug("Problem WhiteboardObject : " + xml);
    } catch (IOException ex) {
      if (logger.isDebugEnabled()) logger.debug("Problem WhiteboardObject : " + xml);
    } catch (Exception ex) {
      if (logger.isDebugEnabled()) logger.debug("Problem WhiteboardObject : " + xml);
    }
  }
    private Color getColor(Element shape) {
      Color color;
      if (shape.hasAttribute(ATR_COLOUR)) {
        String s = shape.getAttribute(ATR_COLOUR);
        if (s.indexOf(',') > -1) {
          String[] rgb = s.split(",");
          color =
              new Color(
                  Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
        } else {
          color = new Color(Integer.parseInt(s));
        }
      } else color = IMPLIED_COLOR;

      if (shape.hasAttribute(ATR_TRANSPARENCY)) {
        int alpha = Integer.parseInt(shape.getAttribute(ATR_TRANSPARENCY));
        if (alpha < 255) return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
      }

      return color;
    }
  @Override
  public void produceVisibleOutput(QContent qc, boolean bInit, boolean bPlain) throws OmException {
    // Get actual current value of string
    String sCurrent = getQuestion().applyPlaceholders(sEquation);

    if (bPlain) {
      // Put text equivalent
      Element eDiv =
          qc.createElement("div"); // Can't use span because they aren't allowed to contain things
      eDiv.setAttribute("style", "display:inline");
      qc.addInlineXHTML(eDiv);
      XML.createText(eDiv, (bSpaceBefore ? " " : "") + getString("alt") + (bSpaceAfter ? " " : ""));
      qc.addTextEquivalent(getString("alt"));

      // Put each placeholder
      for (int i = 0; i < apPlaces.length; i++) {
        // Check content is not hidden
        Place p = apPlaces[i];
        if ((!p.bImplicit && !p.qc.isDisplayed()) || (p.bImplicit && !p.qc.isChildDisplayed()))
          continue;

        // Label followed by content of placeholder
        Element ePlace = XML.createChild(eDiv, "div");
        if (p.sLabel != null) {
          Element eLabel = XML.createChild(ePlace, p.sLabelFor == null ? "span" : "label");
          XML.createText(eLabel, p.sLabel + " ");
          qc.addTextEquivalent(p.sLabel);
          if (p.sLabelFor != null)
            eLabel.setAttribute(
                "for", LabelComponent.getLabel(getQDocument(), bPlain, p.sLabelFor));
        }
        qc.setParent(ePlace);
        p.qc.produceOutput(qc, bInit, bPlain);
        qc.unsetParent();
      }
    } else {
      // Check background colour, foreground, and zoom
      Color cBackground = getBackground();
      if (cBackground == null) cBackground = Color.white;
      Color cForeground =
          getQuestion().isFixedColour()
              ? convertRGB(getQuestion().getFixedColourFG())
              : Color.black;
      double dZoom = getQuestion().getZoom();

      // Hash to filename/identifier
      String sFilename =
          "eq"
              + (sCurrent.hashCode()
                  + cBackground.hashCode() * 3
                  + cForeground.hashCode() * 7
                  + (new Double(dZoom)).hashCode() * 11)
              + (getBoolean(PROPERTY_TEXTFONT) ? "t" : "e")
              + ".png";

      // Make actual image if needed, also get placeholder positions
      if (!sFilename.equals(sSent)) {
        e = Equation.create(sCurrent, (float) dZoom);
        if (getBoolean(PROPERTY_TEXTFONT)) e.setFont("Verdana", new int[] {13, 11, 9});
        BufferedImage bi = e.render(cForeground, cBackground, true);
        qc.addResource(sFilename, "image/png", QContent.convertPNG(bi));
        for (int i = 0; i < apPlaces.length; i++) {
          Point p = e.getPlaceholder(apPlaces[i].sID);
          apPlaces[i].iActualX = p.x;
          apPlaces[i].iActualY = p.y;
        }
        sSent = sFilename;
      }

      Element eEnsureSpaces = qc.createElement("div");
      eEnsureSpaces.setAttribute("class", "equation");
      qc.addInlineXHTML(eEnsureSpaces);

      // If there's a space before, add one here too (otherwise IE eats it)
      if (bSpaceBefore) XML.createText(eEnsureSpaces, " ");

      String sImageID = QDocument.ID_PREFIX + getID() + "_img";
      Element eImg = XML.createChild(eEnsureSpaces, "img");
      eImg.setAttribute("id", sImageID);
      eImg.setAttribute("onmousedown", "return false;"); // Prevent Firefox drag/drop
      eImg.setAttribute("src", "%%RESOURCES%%/" + sFilename);
      eImg.setAttribute("alt", getString("alt"));
      eImg.setAttribute("style", "vertical-align:-" + (e.getHeight() - e.getBaseline()) + "px;");

      if (bSpaceAfter) XML.createText(eEnsureSpaces, " ");

      qc.addTextEquivalent(getString("alt"));

      String sJavascript = "addOnLoad(function() { inlinePositionFix('" + sImageID + "'";

      for (int i = 0; i < apPlaces.length; i++) {
        Place p = apPlaces[i];

        // Must get the label even though not using it, just to indicate that
        // it's been used
        if (p.sLabelFor != null) LabelComponent.getLabel(getQDocument(), bPlain, p.sLabelFor);

        int iEffectiveWidth = (int) Math.round(dZoom * p.iWidth),
            iEffectiveHeight = (int) Math.round(dZoom * p.iHeight);

        String sPlaceholderID = QDocument.ID_PREFIX + getID() + "_" + p.sID;
        Element ePlace = XML.createChild(eEnsureSpaces, "div");
        ePlace.setAttribute("class", "placeholder");
        ePlace.setAttribute("id", sPlaceholderID);
        ePlace.setAttribute(
            "style",
            "width:"
                + iEffectiveWidth
                + "px; "
                + "height:"
                + iEffectiveHeight
                + "px; "
                + "visibility:hidden;");

        QComponent qcPlaceComponent = p.qc;

        if (qcPlaceComponent.isPropertyDefined(PROPERTY_FORCEWIDTH)
            && qcPlaceComponent.isPropertyDefined(PROPERTY_FORCEHEIGHT)) {
          qcPlaceComponent.setInteger(PROPERTY_FORCEWIDTH, iEffectiveWidth);
          qcPlaceComponent.setInteger(PROPERTY_FORCEHEIGHT, iEffectiveHeight);
        }

        if (p.sLabel != null) qc.addTextEquivalent(p.sLabel);
        qc.setParent(ePlace);
        qcPlaceComponent.produceOutput(qc, bInit, bPlain);
        qc.unsetParent();

        sJavascript += ",['" + sPlaceholderID + "'," + p.iActualX + "," + p.iActualY + "]";
      }
      sJavascript += "); });";

      if (apPlaces.length > 0) // No JS needed if there weren't any placeholders
      {
        Element eScript = XML.createChild(eEnsureSpaces, "script");
        eScript.setAttribute("type", "text/javascript");
        XML.createText(eScript, sJavascript);
      }
    }
  }