static function insertHTML($filename, $html, $placeholder = '%CONTENT%')
    {
        if (!strlen($html)) {
            return FALSE;
        }
        $xml = self::getXML($filename);
        if (!$xml) {
            trigger_error("Couldn't get Content XML from {$filename}");
            return FALSE;
        }
        $dom = new DOMDocument();
        $dom->loadXML($xml);
        $dom->preserveWhiteSpace = false;
        $dom->formatOutput = true;
        // Find where to insert into the document
        $insertPoint = NULL;
        foreach ($dom->getElementsByTagNameNS('urn:oasis:names:tc:opendocument:xmlns:text:1.0', 'p') as $elt) {
            if (trim($elt->textContent) == $placeholder) {
                $insertPoint = $elt;
                break;
            }
        }
        if (NULL === $insertPoint) {
            trigger_error("Could not find {$placeholder} in a paragraph to insert the new content");
            return FALSE;
        }
        $paraStyle = $insertPoint->getAttribute('text:style-name');
        // ADD STYLES
        $styleData = array('JethroBold' => array('fo:font-weight' => 'bold'), 'JethroItalic' => array('fo:font-style' => 'italic'), 'JethroUnderline' => array('style:text-underline-style' => 'solid', 'style:text-underline-width' => 'auto', 'style:text-underline-color' => 'font-color'), 'JethroSmall' => array('fo:font-size' => '6pt'));
        $aStylesElt = $dom->getElementsByTagNameNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'automatic-styles')->item(0);
        foreach ($styleData as $styleName => $attrs) {
            $newStyle = $dom->createElement('style:style');
            $newStyle->setAttribute('style:name', $styleName);
            if ($styleName == 'JethroSmall') {
                $newStyle->setAttribute('style:family', 'paragraph');
                $newStyle->setAttribute('style:parent-style-name', $paraStyle);
            } else {
                $newStyle->setAttribute('style:family', 'text');
            }
            $aStylesElt->appendChild($newStyle);
            $newTp = $dom->createElement('style:text-properties');
            foreach ($attrs as $key => $val) {
                $newTp->setAttribute($key, $val);
                if (0 === strpos('fo:', $key)) {
                    $newTp->setAttribute($key . '-asian', $val);
                    $newTp->setAttribute($key . '-complex', $val);
                }
            }
            $newStyle->appendChild($newTp);
        }
        // ADD CONTENT
        $map = array('p' => array('text:p', array('text:style-name' => $paraStyle)), 'br' => array('text:line-break', array()), 'b' => array('text:span', array('text:style-name' => 'JethroBold')), 'strong' => array('text:span', array('text:style-name' => 'JethroBold')), 'i' => array('text:span', array('text:style-name' => 'JethroItalic')), 'em' => array('text:span', array('text:style-name' => 'JethroItalic')), 'u' => array('text:span', array('text:style-name' => 'JethroUnderline')), 'small' => array('text:p', array('text:style-name' => 'JethroSmall')));
        for ($i = 1; $i <= 6; $i++) {
            $map['h' . $i] = array('text:h', array('text:outline-level' => $i));
        }
        $input = strip_tags($html, '<' . implode('>,<', array_keys($map)) . '>');
        $input = preg_replace('#[>]\\s+#', '>', $input);
        // strip space after tags which would cause funny indents
        $input = '<body>' . $input . '</body>';
        $htmlDom = new DOMDocument();
        $htmlDom->preserveWhiteSpace = false;
        $htmlDom->formatOutput = true;
        $htmlDom->loadHTML($input);
        $newTags = array();
        foreach ($map as $from => $to) {
            list($newTag, $newAttrs) = $to;
            ODF_Tools::renameTags($htmlDom, $from, $newTag, $newAttrs, 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
            $newTags[] = $newTag;
        }
        // Now we need to get the result XML text and load it into a real XML DOM, not an HTML one
        $fragmentXML = $htmlDom->saveXML();
        $begin = strpos($fragmentXML, '<body>') + 6;
        $length = strpos($fragmentXML, '</body>') - $begin;
        $fragmentXML = '<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" office:version="1.2">
		' . substr($fragmentXML, $begin, $length) . '</office:document-content>';
        $fragmentDom = new DOMDocument();
        $fragmentDom->loadXML($fragmentXML);
        foreach ($fragmentDom->firstChild->childNodes as $newNode) {
            $newNew = $dom->importNode($newNode, true);
            $insertPoint->parentNode->insertBefore($newNew, $insertPoint);
        }
        $insertPoint->parentNode->removeChild($insertPoint);
        return ODF_Tools::setXML($filename, $dom->saveXML());
    }
 function mergeODT($source_file, $merged_file)
 {
     $xml_filename = 'content.xml';
     require_once 'include/odf_tools.class.php';
     $content = ODF_Tools::getXML($source_file, $xml_filename);
     if (empty($content)) {
         trigger_error('Could not find content within this ' . $extension . ' file');
         return;
     }
     $HEADER_END = '</text:sequence-decls>';
     $FOOTER_START = '</office:text>';
     $middle_start_pos = strpos($content, $HEADER_END) + strlen($HEADER_END);
     $middle_end_pos = strpos($content, $FOOTER_START);
     if (NULL === $middle_start_pos || NULL === $middle_end_pos) {
         trigger_error('Cannot locate body content of the file');
         return;
     }
     $middle_template = substr($content, $middle_start_pos, $middle_end_pos - $middle_start_pos);
     $header = substr($content, 0, $middle_start_pos);
     $footer = substr($content, $middle_end_pos);
     $merged_middle = '';
     foreach ($this->getMergeData() as $id => $row) {
         if (empty($row)) {
             continue;
         }
         $this_middle = $middle_template;
         foreach ($row as $k => $v) {
             $this_middle = str_replace('%' . strtoupper($k) . '%', ODF_Tools::odfEntities(trim($v)), $this_middle);
         }
         $merged_middle .= $this_middle;
     }
     $merged_file = dirname($source_file) . '/jethro_merged_' . time() . session_id();
     copy($source_file, $merged_file);
     ODF_Tools::setXML($merged_file, $header . $merged_middle . $footer, $xml_filename);
 }