');
$lengthWithName = strlen ('', $first);
if ( $endFirst === false ) {
break;
}
$second = strpos ($content, '', $endFirst);
if ( $second === false ) {
break;
}
// $match includes the whole tag 'text'
// The attribute 'name' is optional!
$match = substr ($content, $first, $second - $first + $length + 1);
$text = substr ($match, $endFirst-$first+1, -($length + 1));
$text = trim ($text, ' ');
$text = strtolower ($text);
$page = str_replace (' ', '_', $text);
$opentag = substr ($match, 0, $endFirst-$first);
$name = substr ($opentag, $lengthWithName);
$name = trim ($name, '">');
$linkStyle = 'text:style-name="'.$styleName.'"';
$linkStyle .= ' text:visited-style-name="'.$visitedStyleName.'"';
$found = false;
foreach ($toc as $item) {
$params = explode (',', $item);
if ( $page == $params [1] ) {
$found = true;
$link = '';
if ( !empty($name) ) {
$link .= $name;
} else {
$link .= $text;
}
$link .= '';
$content = str_replace ($match, $link, $content);
$position = $first + strlen ($link);
}
}
if ( $found == false ) {
// Nothing found yet, check the bookmarks too.
foreach ($bookmarks as $item) {
if ( $page == $item ) {
$found = true;
$link = '';
if ( !empty($name) ) {
$link .= $name;
} else {
$link .= $text;
}
$link .= '';
$content = str_replace ($match, $link, $content);
$position = $first + strlen ($link);
}
}
}
if ( $found == false ) {
// If we get here, then the referenced target was not found.
// There must be a bug manging the bookmarks or links!
// At least remove the locallink element and insert text.
if ( !empty($name) ) {
$content = str_replace ($match, $name, $content);
} else {
$content = str_replace ($match, $text, $content);
}
$position = $first + strlen ($text);
}
}
}
/**
* This function deletes the useless elements. Right now, these are empty paragraphs
* or paragraphs that only include whitespace.
*
* IMPORTANT:
* Paragraphs can be used for pagebreaks/changing page format.
* Such paragraphs may not be deleted!
*
* @param string $docContent The document content
* @param array $preventDeletetionStyles Array of style names which may not be deleted
*/
public static function deleteUselessElements(&$docContent, array $preventDeletetionStyles) {
$length_open = strlen ('');
$max = strlen ($docContent);
$pos = 0;
while ($pos < $max) {
$start_open = strpos ($docContent, '', $start_open + $length_open);
if ( $start_close === false ) {
break;
}
$end = strpos ($docContent, '', $start_close + 1);
if ( $end === false ) {
break;
}
$deleted = false;
$length = $end - $start_open + $length_close;
$content = substr ($docContent, $start_close + 1, $end - ($start_close + 1));
if ( empty($content) || ctype_space ($content) ) {
// Paragraph is empty or consists of whitespace only. Check style name.
$style_start = strpos ($docContent, '"', $start_open);
if ( $style_start === false ) {
// No '"' found??? Ignore this paragraph.
break;
}
$style_end = strpos ($docContent, '"', $style_start+1);
if ( $style_end === false ) {
// No '"' found??? Ignore this paragraph.
break;
}
$style_name = substr ($docContent, $style_start+1, $style_end - ($style_start+1));
// Only delete empty paragraph if not listed in 'Do not delete' array!
if ( !in_array($style_name, $preventDeletetionStyles) )
{
$docContent = substr_replace($docContent, '', $start_open, $length);
$deleted = true;
$max -= $length;
$pos = $start_open;
}
}
if ( $deleted == false ) {
$pos = $start_close;
}
}
}
/**
* The function tries to examine the width and height
* of the image stored in file $src.
*
* @param string $src The file name of image
* @param int $maxwidth The maximum width the image shall have
* @param int $maxheight The maximum height the image shall have
* @return array Width and height of the image in centimeters or
* both 0 if file doesn't exist.
* Just the integer value, no units included.
*/
public static function getImageSize($src, $maxwidth=NULL, $maxheight=NULL){
if (file_exists($src)) {
$info = getimagesize($src);
if(!$width){
$width = $info[0];
$height = $info[1];
}else{
$height = round(($width * $info[1]) / $info[0]);
}
if ($maxwidth && $width > $maxwidth) {
$height = $height * ($maxwidth/$width);
$width = $maxwidth;
}
if ($maxheight && $height > $maxheight) {
$width = $width * ($maxheight/$height);
$height = $maxheight;
}
// Convert from pixel to centimeters
if ($width) $width = (($width/96.0)*2.54);
if ($height) $height = (($height/96.0)*2.54);
return array($width, $height);
}
return array(0, 0);
}
/**
* Return the size of an image in centimeters.
*
* @param string $src Filepath of the image
* @param string|null $width Alternative width
* @param string|null $height Alternative height
* @param boolean|true $preferImage Prefer original image size
* @param ODTUnits $units $ODTUnits object for unit conversion
* @return array
*/
public static function getImageSizeString($src, $width = NULL, $height = NULL, $preferImage=true, ODTUnits $units){
list($width_file, $height_file) = self::getImageSize($src);
// Get original ratio if possible
$ratio = 1;
if ($width_file != 0 && $height_file != 0) {
$ratio = $height_file/$width_file;
}
if ($width_file != 0 && $preferImage) {
$width = $width_file.'cm';
$height = $height_file.'cm';
} else {
// convert from pixel to centimeters only if no unit is
// specified or if unit is 'px'
$unit_width = $units->stripDigits ($width);
$unit_height = $units->stripDigits ($height);
if ((empty($unit_width) && empty($unit_height)) ||
($unit_width == 'px' && $unit_height == 'px')) {
if (!$height) {
$height = $width * $ratio;
}
$height = (($height/96.0)*2.54).'cm';
if ($width) $width = (($width/96.0)*2.54).'cm';
}
}
// At this point $width and $height should include a unit
$width = str_replace(',', '.', $width);
$height = str_replace(',', '.', $height);
if ($width && $height) {
// Don't be wider than the page
if ($width >= 17){ // FIXME : this assumes A4 page format with 2cm margins
$width = $width.'" style:rel-width="100%';
$height = $height.'" style:rel-height="scale';
} else {
$width = $width;
$height = $height;
}
} else {
// external image and unable to download, fallback
if (!$width) {
$width = '" svg:rel-width="100%';
}
if (!$height) {
$height = '" svg:rel-height="100%';
}
}
return array($width, $height);
}
/**
* Split $value by whitespace and convert any relative values (%)
* into an absolute value. This is done by taking the percentage of
* $maxWidthInPt.
*
* @param string $value String (Property value)
* @param integer $maxWidthInPt Maximum width in points
* @param ODTUnits $units $ODTUnits object for unit conversion
* @return string
*/
protected static function adjustPercentageValueParts ($value, $maxWidthInPt, $units) {
$values = preg_split ('/\s+/', $value);
$value = '';
foreach ($values as $part) {
$length = strlen ($part);
if ( $length > 1 && $part [$length-1] == '%' ) {
$percentageValue = $units->getDigits($part);
$part = (($percentageValue * $maxWidthInPt)/100) . 'pt';
//$part = '5pt ';
}
$value .= ' '.$part;
}
$value = trim($value);
$value = trim($value, '"');
return $value;
}
/**
* The function adjusts the properties values for ODT:
* - 'em' units are converted to 'pt' units
* - CSS color names are converted to its RGB value
* - short color values like #fff are converted to the long format, e.g #ffffff
* - some relative values are converted to absoulte depending on other
* values e.g. 'line-height' an 'font-size'
*
* @author LarsDW223
*
* @param array $properties Array with property value pairs
* @param ODTUnits $units Units object to use for conversion
* @param integer $maxWidth Units object to use for conversion
*/
public static function adjustValuesForODT (&$properties, ODTUnits $units, $maxWidth=NULL) {
$adjustToMaxWidth = array('margin', 'margin-left', 'margin-right', 'margin-top', 'margin-bottom');
// Convert 'text-decoration'.
if (isset($properties ['text-decoration'])) {
switch ($properties ['text-decoration']) {
case 'line-through':
$properties ['text-line-through-style'] = 'solid';
break;
case 'underline':
$properties ['text-underline-style'] = 'solid';
break;
case 'overline':
$properties ['text-overline-style'] = 'solid';
break;
}
}
// Normalize border properties
cssborder::normalize($properties);
// First do simple adjustments per property
foreach ($properties as $property => $value) {
$properties [$property] = self::adjustValueForODT ($property, $value, $units);
}
// Adjust relative margins if $maxWidth is given.
// $maxWidth is expected to be the width of the surrounding element.
if (isset($maxWidth)) {
$maxWidthInPt = $units->toPoints($maxWidth, 'y');
$maxWidthInPt = $units->getDigits($maxWidthInPt);
foreach ($adjustToMaxWidth as $property) {
if (!empty($properties [$property])) {
$properties [$property] = self::adjustPercentageValueParts ($properties [$property], $maxWidthInPt, $units);
}
}
}
// Now we do the adjustments for which one value depends on another
// Do we have font-size or line-height set?
if (isset($properties ['font-size']) || isset($properties ['line-height'])) {
// First get absolute font-size in points
$base_font_size_in_pt = $units->getPixelPerEm ().'px';
$base_font_size_in_pt = $units->toPoints($base_font_size_in_pt, 'y');
$base_font_size_in_pt = $units->getDigits($base_font_size_in_pt);
if (isset($properties ['font-size'])) {
$font_size_unit = $units->stripDigits($properties ['font-size']);
$font_size_digits = $units->getDigits($properties ['font-size']);
if ($font_size_unit == '%') {
$base_font_size_in_pt = ($font_size_digits * $base_font_size_in_pt)/100;
$properties ['font-size'] = $base_font_size_in_pt.'pt';
} elseif ($font_size_unit != 'pt') {
$properties ['font-size'] = $units->toPoints($properties ['font-size'], 'y');
$base_font_size_in_pt = $units->getDigits($properties ['font-size']);
} else {
$base_font_size_in_pt = $units->getDigits($properties ['font-size']);
}
}
// Convert relative line-heights to absolute
if (isset($properties ['line-height'])) {
$line_height_unit = $units->stripDigits($properties ['line-height']);
$line_height_digits = $units->getDigits($properties ['line-height']);
if ($line_height_unit == '%') {
$properties ['line-height'] = (($line_height_digits * $base_font_size_in_pt)/100).'pt';
} elseif (empty($line_height_unit)) {
$properties ['line-height'] = ($line_height_digits * $base_font_size_in_pt).'pt';
}
}
}
}
/**
* The function adjusts the property value for ODT:
* - 'em' units are converted to 'pt' units
* - CSS color names are converted to its RGB value
* - short color values like #fff are converted to the long format, e.g #ffffff
*
* @author LarsDW223
*
* @param string $property The property name
* @param string $value The value
* @param ODTUnits $units Units object to use for conversion
* @return string Converted value
*/
public static function adjustValueForODT ($property, $value, ODTUnits $units) {
if ($property == 'font-family') {
// There might be several font/font-families included.
// Only take the first one.
$value = trim($value, '"');
if (strpos($value, ',') !== false) {
$values = explode(',', $value);
$value = trim ($values [0], '"');
$value = trim ($value, "'");
$value = trim ($value);
}
} else {
$values = preg_split ('/\s+/', $value);
$value = '';
foreach ($values as $part) {
$length = strlen ($part);
// If it is a short color value (#xxx) then convert it to long value (#xxxxxx)
// (ODT does not support the short form)
if ( $part [0] == '#' && $length == 4 ) {
$part = '#'.$part [1].$part [1].$part [2].$part [2].$part [3].$part [3];
} else {
// If it is a CSS color name, get it's real color value
$color = csscolors::getColorValue ($part);
if ( $part == 'black' || $color != '#000000' ) {
$part = $color;
}
}
if ( $length > 2 && $part [$length-2] == 'e' && $part [$length-1] == 'm' ) {
$part = $units->toPoints($part, 'y');
}
if ( $length > 2 && ($part [$length-2] != 'p' || $part [$length-1] != 't') &&
strpos($property, 'border')!==false ) {
$part = $units->toPoints($part, 'y');
}
// Some values can have '"' in it. These need to be converted to '''
// e.g. 'font-family' tp specify that '"Courier New"' is one font name not two
$part = str_replace('"', ''', $part);
$value .= ' '.$part;
}
$value = trim($value);
$value = trim($value, '"');
}
return $value;
}
/**
* This function processes the CSS style declarations in $style and saves them in $properties
* as key - value pairs, e.g. $properties ['color'] = 'red'. It also adjusts the values
* for the ODT format and changes URLs to local paths if required, using $baseURL).
*
* @author LarsDW223
* @param array $properties
* @param string $style The CSS style e.g. 'color:red;'
* @param string|null $baseURL
* @param ODTUnits $units Units object to use for conversion
* @param integer $maxWidth MaximumWidth
*/
public static function getCSSStylePropertiesForODT(&$properties, $style, $baseURL = NULL, ODTUnits $units, $maxWidth=NULL){
// Create rule with selector '*' (doesn't matter) and declarations as set in $style
$rule = new css_rule ('*', $style);
$rule->getProperties ($properties);
//foreach ($properties as $property => $value) {
// $properties [$property] = self::adjustValueForODT ($property, $value, $units);
//}
self::adjustValuesForODT ($properties, $units, $maxWidth);
if ( !empty ($properties ['background-image']) ) {
if ( !empty ($baseURL) ) {
// Replace 'url(...)' with $baseURL
$properties ['background-image'] = cssimportnew::replaceURLPrefix ($properties ['background-image'], $baseURL);
}
}
}
/**
* The function opens/puts a new element on the HTML stack in $params->htmlStack.
* The element name will be $element and it will be created with the attributes $attributes.
* Then CSS matching is performed and the CSS properties are returned in $dest.
* Finally the CSS properties are converted to ODT format if neccessary.
*
* @author LarsDW223
* @param ODTInternalParams $params Commom params.
* @param array $dest Target array for properties storage
* @param string $element The element's name
* @param string $attributes The element's attributes
* @param integer $maxWidth Maximum Width
*/
public static function openHTMLElement (ODTInternalParams $params, array &$dest, $element, $attributes, $maxWidth=NULL) {
// Push/create our element to import on the stack
$params->htmlStack->open($element, $attributes);
$toMatch = $params->htmlStack->getCurrentElement();
$params->import->getPropertiesForElement($dest, $toMatch, $params->units);
// Adjust values for ODT
self::adjustValuesForODT($dest, $params->units, $maxWidth);
}
/**
* The function closes element with name $element on the HTML stack in $params->htmlStack.
*
* @author LarsDW223
* @param ODTInternalParams $params Commom params.
* @param string $element The element's name
*/
public static function closeHTMLElement (ODTInternalParams $params, $element) {
$params->htmlStack->close($element);
}
/**
* The function temporarily opens/puts a new element on the HTML stack in $params->htmlStack.
* Before leaving the function the element is removed from the stack.
*
* The element name will be $element and it will be created with the attributes $attributes.
* After opening the element CSS matching is performed and the CSS properties are returned in $dest.
* Finally the CSS properties are converted to ODT format if neccessary.
*
* @author LarsDW223
* @param ODTInternalParams $params Commom params.
* @param array $dest Target array for properties storage
* @param string $element The element's name
* @param string $attributes The element's attributes
* @param integer $maxWidth Maximum Width
* @param boolean $inherit Enable/disable CSS inheritance
*/
public static function getHTMLElementProperties (ODTInternalParams $params, array &$dest, $element, $attributes, $maxWidth=NULL, $inherit=true) {
// Push/create our element to import on the stack
$params->htmlStack->open($element, $attributes);
$toMatch = $params->htmlStack->getCurrentElement();
$params->import->getPropertiesForElement($dest, $toMatch, $params->units, $inherit);
// Adjust values for ODT
self::adjustValuesForODT($dest, $params->units, $maxWidth);
// Remove element from stack
$params->htmlStack->removeCurrent();
}
/**
* Small helper function for finding the next tag enclosed in brackets.
* Returns beginning and end of the tag as an array [0] = start, [1] = end.
*
* @author LarsDW223
* @param string $content Code to search in.
* @param string $pos Start position for searching.
* @return array
*/
public static function getNextTag (&$content, $pos) {
$start = strpos ($content, '<', $pos);
if ($start === false) {
return false;
}
$end = strpos ($content, '>', $pos);
if ($end === false) {
return false;
}
return array($start, $end);
}
/**
* The function returns $value as a valid IRI and replaces some signs
* if neccessary, e.g. '&' will be replaced by '&'.
* The function will not do double replacements, e.g. if the string
* already includes a '&' it will NOT become '&'.
*
* @author LarsDW223
* @param string $value String to be converted to IRI
* @return string
*/
public static function stringToIRI ($value) {
$max = strlen ($value);
for ($pos = 0 ; $pos < $max ; $pos++) {
switch ($value [$pos]) {
case '&':
if ($max - $pos >= 4 &&
$value [$pos+1] == '#' &&
$value [$pos+2] == '3' &&
$value [$pos+3] == '8' &&
$value [$pos+4] == ';') {
// '&' must be replaced with "&"
$value [$pos+1] = 'a';
$value [$pos+2] = 'm';
$value [$pos+3] = 'p';
$pos += 4;
} else if ($max - $pos < 4 ||
$value [$pos+1] != 'a' ||
$value [$pos+2] != 'm' ||
$value [$pos+3] != 'p' ||
$value [$pos+4] != ';' ) {
// '&' must be replaced with "&"
$new = substr($value, 0, $pos+1);
$new .= 'amp;';
$new .= substr($value, $pos+1);
$value = $new;
$max += 4;
$pos += 4;
}
break;
}
}
return $value;
}
protected static function getLinkURL ($search) {
preg_match ('/href="[^"]*"/', $search, $matches);
$url = substr ($matches[0], 5);
$url = trim($url, '"');
// Keep '&' and ':' in the link URL unescaped, otherwise url parameter passing will not work
$url = str_replace('&', '&', $url);
$url = str_replace('%3A', ':', $url);
return $url;
}
/**
* static call back to replace spaces
*
* @param array $matches
* @return string
*/
protected static function _preserveSpace($matches){
$spaces = $matches[1];
$len = strlen($spaces);
return '';
}
protected static function createTextStyle (ODTInternalParams $params, $element, $attributes, $styleName=NULL) {
// Create automatic style
if (!isset($styleName) || !$params->document->styleExists($styleName)) {
// Get properties
$properties = array();
self::getHTMLElementProperties ($params, $properties, $element, $attributes);
if (!isset($styleName)) {
$properties ['style-name'] = ODTStyle::getNewStylename ('span');
} else {
// Use callers style name. He needs to be sure that it's unique!
$properties ['style-name'] = $styleName;
}
$params->document->createTextStyle($properties, false);
// Return style name
return $properties ['style-name'];
} else {
// Style already exists
return $styleName;
}
}
protected static function createParagraphStyle (ODTInternalParams $params, $element, $attributes, $styleName=NULL) {
// Create automatic style
if (!isset($styleName) || !$params->document->styleExists($styleName)) {
// Get properties
$properties = array();
self::getHTMLElementProperties ($params, $properties, $element, $attributes);
if (!isset($styleName)) {
$properties ['style-name'] = ODTStyle::getNewStylename ('span');
} else {
// Use callers style name. He needs to be sure that it's unique!
$properties ['style-name'] = $styleName;
}
$params->document->createParagraphStyle($properties, false);
// Return style name
return $properties ['style-name'];
} else {
// Style already exists
return $styleName;
}
}
/**
* Convenience function for converting some HTML code to ODT format.
* The function will try to automatically create any needed ODT styles
* from the CSS code found in the HTML code.
*
* Also some special settings can be passed in the options array:
*
* $options ['p_style']:
* The default paragraph style. If empty 'body' will be used.
*
* $options ['list_p_style']:
* The default paragraph style in lists. If empty 'body' will be used.
*
* $options ['list_ol_style']:
* The default style for ordered lists. If empty 'numbering' will be used.
*
* $options ['list_ul_style']:
* The default style for un-ordered lists. If empty 'list' will be used.
*
* $options ['media_selector']:
* The media selector used for CSS handling (e.g. 'screen' or 'print').
* If empty the current/configured one will be used.
*
* $options ['element']:
* If not empty an HTML tag named '$options ['element']' will be pushed
* on the internal HTML stack before converting the $HTMLCode.
* This influences CSS handling.
*
* $options ['attributes']:
* The attributes to set for '$options ['element']'.
*
* $options ['escape_content']:
* Should have the value 'true' or 'false' (as string!). If 'true'
* XML entities will be escaped. Otherwise it is assumed that it
* already has been done.
*
* $options ['class']:
* Optional CSS class to add to found 'class="..."' attributes in
* the HTML code.
*
* $options ['style_names']:
* If set to 'prefix_and_class' then ODT style names will not be
* generated dynamically but are constructed from '$options ['style_names_prefix']'
* following the CSS class name(s).
*
* $options ['linebreaks']:
* If set to 'remove' then linebreaks will be ignored. Otherwise
* they will be kept and converted to proper ODT linebreaks.
*
* $options ['tabs']:
* If set to 'remove' then tabs will be ignored. Otherwise they
* will be kept and converted to proper ODT tabs.
*
* $options ['space']:
* If set to 'preserve' then space is preserved like for preformatted
* code blocks. Otherwise space is not preserved and multiple spaces
* will apear as only one space.
*
* @author LarsDW223
* @param ODTInternalParams $params The internal params
* @param string $HTMLCode The HTML code to convert
* @param array $options Array of options
*/
public static function generateODTfromHTMLCode(ODTInternalParams $params, $HTMLCode, array $options){
$elements = array ('sup' => array ('open' => '',
'close' => ''),
'sub' => array ('open' => '',
'close' => ''),
'u' => array ('open' => '',
'close' => ''),
'em' => array ('open' => '',
'close' => ''),
'strong' => array ('open' => '',
'close' => ''),
'del' => array ('open' => '',
'close' => ''),
'span' => array ('open' => '',
'close' => ''),
'a' => array ('open' => '',
'close' => ''),
'ol' => array ('open' => '',
'close' => ''),
'ul' => array ('open' => '',
'close' => ''),
'li' => array ('open' => '',
'close' => ''),
// In the moment only remove divs
'div' => array ('open' => '', 'close' => ''),
);
$parsed = array();
// remove useless leading and trailing whitespace-newlines
$HTMLCode = preg_replace('/^ \n/', '', $HTMLCode);
$HTMLCode = preg_replace('/\n $/', '', $HTMLCode);
$HTMLCode = str_replace(' ', ' ', $HTMLCode);
// Get default paragraph style
if (!empty($options ['p_style'])) {
$p_style = $options ['p_style'];
} else {
$p_style = $params->document->getStyleName('body');
}
// Get default list style names
if (!empty($options ['list_p_style'])) {
$p_list_style = $options ['list_p_style'];
} else {
$p_list_style = $params->document->getStyleName('body');
}
if (!empty($options ['list_ol_style'])) {
$ol_list_style = $options ['list_ol_style'];
} else {
$ol_list_style = $params->document->getStyleName('numbering');
}
if (!empty($options ['list_ul_style'])) {
$ul_list_style = $options ['list_ul_style'];
} else {
$ul_list_style = $params->document->getStyleName('list');
}
// Set new media selector (remember old one)
$media = $params->import->getMedia ();
if (!empty($options['media_selector'])) {
$params->import->setMedia($options['media_selector']);
}
if (!empty($options ['element'])) {
$params->htmlStack->open($options ['element'], $options ['attributes']);
}
// First examine $HTMLCode and differ between normal content,
// opening tags and closing tags.
$max = strlen ($HTMLCode);
$pos = 0;
while ($pos < $max) {
$found = self::getNextTag($HTMLCode, $pos);
if ($found !== false) {
$entry = array();
$entry ['content'] = substr($HTMLCode, $pos, $found [0]-$pos);
if ($entry ['content'] === false) {
$entry ['content'] = '';
}
$parsed [] = $entry;
$tagged = substr($HTMLCode, $found [0], $found [1]-$found [0]+1);
$entry = array();
if ($HTMLCode [$found[1]-1] == '/') {
// Element without content , doesn'T make sense, save as content
$entry ['content'] = $tagged;
} else {
if ($HTMLCode [$found[0]+1] != '/') {
$parts = explode(' ', trim($tagged, '<> '), 2);
$entry ['tag-open'] = $parts [0];
if ( isset($parts [1]) ) {
$entry ['attributes'] = $parts [1];
}
$entry ['tag-orig'] = $tagged;
} else {
$entry ['tag-close'] = trim ($tagged, '<>/ ');
$entry ['tag-orig'] = $tagged;
}
}
$entry ['matched'] = false;
$parsed [] = $entry;
$pos = $found [1]+1;
} else {
$entry = array();
$entry ['content'] = substr($HTMLCode, $pos);
$parsed [] = $entry;
break;
}
}
// Check each array entry.
$checked = array();
$first = true;
$firstTag = '';
$olStartValue = NULL;
for ($out = 0 ; $out < count($parsed) ; $out++) {
if (isset($checked [$out])) {
continue;
}
$found = &$parsed [$out];
if (isset($found ['content'])) {
if ($options ['escape_content'] !== 'false') {
$checked [$out] = $params->document->replaceXMLEntities($found ['content']);
} else {
$checked [$out] = $found ['content'];
}
} else if (isset($found ['tag-open'])) {
$closed = false;
for ($in = $out+1 ; $in < count($parsed) ; $in++) {
$search = &$parsed [$in];
if (isset($search ['tag-close']) &&
$found ['tag-open'] == $search ['tag-close'] &&
$search ['matched'] === false &&
array_key_exists($found ['tag-open'], $elements)) {
$closed = true;
$search ['matched'] = true;
// Remeber the first element
if ($first) {
$first = false;
$firstTag = $found ['tag-open'];
}
// Known and closed tag, convert to ODT
switch ($found ['tag-open']) {
case 'span':
// Create ODT span using CSS style from attributes
if (!empty($options ['class'])) {
if (preg_match('/class="[^"]*"/', $found ['attributes'], $matches) == 1) {
$class_attr = substr($matches [0], 7);
$class_attr = trim($class_attr, '"');
$class_attr = 'class="'.$options ['class'].' '.$class_attr.'"';
$found ['attributes'] = str_replace($matches [0], $class_attr, $found ['attributes']);
}
}
$style_name = NULL;
if ($options ['style_names'] == 'prefix_and_class') {
if (preg_match('/class="[^"]*"/', $found ['attributes'], $matches) == 1) {
$class_attr = substr($matches [0], 7);
$class_attr = trim($class_attr, '"');
$style_name = $options ['style_names_prefix'].$class_attr;
}
}
$style_name = self::createTextStyle ($params, 'span', $found ['attributes'], $style_name);
$checked [$out] = '';
$checked [$in] = '';
break;
case 'a':
$url = self::getLinkURL($found ['attributes']);
if (empty($url)) {
$url = 'URLNotFoundInXHTMLLink';
}
$checked [$out] = $params->document->openHyperlink ($url, NULL, NULL, true);
$checked [$in] = $params->document->closeHyperlink (true);
break;
case 'ul':
$checked [$out] = '';
$checked [$in] = '';
break;
case 'ol':
$checked [$out] = '';
$checked [$in] = '';
if (preg_match('/start="[^"]*"/', $found ['attributes'], $matches) == 1) {
$olStartValue = substr($matches [0], 7);
$olStartValue = trim($olStartValue, '"');
}
break;
case 'li':
// Create ODT span using CSS style from attributes
$haveClass = false;
if (!empty($options ['class'])) {
if (preg_match('/class="[^"]*"/', $found ['attributes'], $matches) == 1) {
$class_attr = substr($matches [0], 7);
$class_attr = trim($class_attr, '"');
$class_attr = 'class="'.$options ['class'].' '.$class_attr.'"';
$found ['attributes'] = str_replace($matches [0], $class_attr, $found ['attributes']);
$haveClass = true;
}
}
$style_name = NULL;
if ($options ['style_names'] == 'prefix_and_class') {
if (preg_match('/class="[^"]*"/', $found ['attributes'], $matches) == 1) {
$class_attr = substr($matches [0], 7);
$class_attr = trim($class_attr, '"');
$style_name = $options ['style_names_prefix'].$class_attr;
$haveClass = true;
}
}
if ($haveClass) {
$style_name = self::createParagraphStyle ($params, 'li', $found ['attributes'], $style_name);
} else {
$style_name = $p_list_style;
}
$checked [$out] = '';
$checked [$in] = '';
break;
default:
// Simple replacement
$checked [$out] = $elements [$found ['tag-open']]['open'];
$checked [$in] = $elements [$found ['tag-open']]['close'];
break;
}
break;
}
}
// Known tag? Closing tag found?
if (!$closed) {
// No, save as content
if ($options ['escape_content'] !== 'false') {
$checked [$out] = $params->document->replaceXMLEntities($found ['tag-orig']);
} else {
$checked [$out] = $found ['tag-orig'];
}
}
} else if (isset($found ['tag-close'])) {
// If we find a closing tag it means it did not match
// an opening tag. Convert to content!
$checked [$out] = $params->document->replaceXMLEntities($found ['tag-orig']);
}
}
// Eventually we need to create an enclosing element, open it
switch ($firstTag) {
case 'ol':
case 'ul':
// Close an eventually open paragraph
$params->document->paragraphClose();
break;
default:
$params->document->paragraphClose();
$params->document->paragraphOpen($p_style);
break;
}
// Add checked entries to content
$content = '';
for ($index = 0 ; $index < count($checked) ; $index++) {
$content .= $checked [$index];
}
// Handle newlines
if ($options ['linebreaks'] !== 'remove') {
$content = str_replace("\n",'',$content);
} else {
$content = str_replace("\n",'',$content);
}
// Handle tabs
if ($options ['tabs'] !== 'remove') {
$content = str_replace("\t",'',$content);
} else {
$content = str_replace("\t",'',$content);
}
// Preserve space?
if ($options ['space'] === 'preserve') {
$content = preg_replace_callback('/( +)/',array(__CLASS__, '_preserveSpace'), $content);
}
$params->content .= $content;
// Eventually we need to create an enclosing element, close it
switch ($firstTag) {
case 'ol':
case 'ul':
// Nothing to do
break;
default:
$params->document->paragraphClose();
break;
}
// Remove current element from stack, if we created one
if (!empty($options ['element'])) {
$params->htmlStack->removeCurrent();
}
// Restore media selector
if (!empty($options ['media_selector'])) {
$params->import->setMedia ($media);
}
}
}