Location: PHPKode > projects > Aukyla Platform > aukyla/base/OpenDocument.php
<?php
/*
     OpenDocument.php, utility functions for working with OpenDocument files and
                       converting them to XHTML.
     Copyright (C) 2003-2005 Arend van Beelen, Auton Rijnsburg

     This program is free software; you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the Free
     Software Foundation; either version 2 of the License, or (at your option)
     any later version.

     This program is distributed in the hope that it will be useful, but WITHOUT
     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     more details.

     You should have received a copy of the GNU General Public License along
     with this program; if not, write to the Free Software Foundation, Inc.,
     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

     For any questions, comments or whatever, you may mail me at: hide@address.com
*/

require_once('Config.php');
require_once('URI.php');

/**
 * @brief Class with utility functions for working with OpenDocument files.
 */
class OpenDocument
{
	/**
	 * Destructor.
	 *
	 * Performs cleanup operations.
	 */
	public function __destruct()
	{
		if($this->fileOpened == true)
		{
			$this->close();
		}
	}

	/**
	 * Loads the specified OpenDocument for use by the other utility
	 * functions in this class.
	 *
	 * If the document has already been loaded, you will receive a cached
	 * reference to it. This reference contains all changes made to the
	 * already loaded document, and any changes you make on this reference
	 * will reflect on the original and vice versa.
	 *
	 * @param openDocument URI to the OpenDocument file to load.
	 *
	 * @return @p true if the document was successfully loaded, @p false
	 *         otherwise.
	 */
	public function load($openDocument)
	{
		if($this->fileOpened == true)
		{
			$this->close();
		}

		if((URI::permissions($openDocument) & PERMISSION_READ) == 0)
		{
			return false;
		}

		$simplifiedFilename = String::simplifyPath($openDocument);
		if(isset(self::$openedDocuments[$simplifiedFilename]))
		{
			$this->fileOpened = true;
			$this->document = $openDocument;
			$this->tmpdir = self::$openedDocuments[$simplifiedFilename]->tmpdir;
			$this->metaDoc = self::$openedDocuments[$simplifiedFilename]->metaDoc;
			$this->metaNodes = self::$openedDocuments[$simplifiedFilename]->metaNodes;
			$this->changed = self::$openedDocuments[$simplifiedFilename]->changed;
			$this->cached = true;
			return true;
		}

		// create a temporary directory and extract the document to it
		$this->tmpdir = tempnam('/tmp', 'OpenDocument');
		unlink($this->tmpdir);
		if(mkdir($this->tmpdir) == false)
		{
			return false;
		}
		if(copy($openDocument, "{$this->tmpdir}/document.od") == false)
		{
			exec("rm -R {$this->tmpdir}");
			trigger_error('Could not copy OpenDocument file');
			return false;
		}
		exec("unzip {$this->tmpdir}/document.od -d {$this->tmpdir}", $output, $return);
		if($return != 0)
		{
			exec("rm -R {$this->tmpdir}");
			return false;
		}

		$this->loadMeta();

		$this->fileOpened = true;
		$this->document = $openDocument;
		$this->cached = false;
		self::$openedDocuments[$simplifiedFilename] = $this;

		return true;
	}

	/**
	 * Unloads the loaded OpenDocument file. If the file has been changed, it
	 * will be saved automatically. Any cached references to it will become
	 * useless and using them after this call will result in undefined
	 * behavior.
	 */
	public function unload()
	{
		$simplifiedFilename = String::simplifyPath($this->document);
		if(isset(self::$openedDocuments[$simplifiedFilename]))
		{
			unset(self::$openedDocuments[$simplifiedFilename]);
		}
		else
		{
			trigger_error('Document was never loaded');
		}
	}

	/**
	 * Returns the title of the currently opened OpenDocument file.
	 *
	 * @return The title of the document as given by the document's author.
	 *
	 * @sa setTitle()
	 */
	public function title()
	{
		if($this->fileOpened == false)
		{
			trigger_error('Trying to get title when no document is loaded');
			return '';
		}

		return $this->metaNodes['title']->nodeValue;
	}

	/**
	 * Sets the title of the currently opened OpenDocument file.
	 *
	 * @param title The new title to use for the document.
	 *
	 * @sa title()
	 */
	public function setTitle($title)
	{
		if($this->fileOpened == false)
		{
			trigger_error('Cannot set title when no document is loaded');
			return;
		}

		$this->metaNodes['title']->nodeValue = $title;

		$this->setChanged();
	}

	/**
	 * Returns the subject of the currently opened OpenDocument file.
	 *
	 * @return The subject of the document as given by the document's author.
	 *
	 * @sa setSubject()
	 */
	public function subject()
	{
		if($this->fileOpened == false)
		{
			trigger_error('Trying to get subject when no document is loaded');
			return '';
		}

		return $this->metaNodes['subject']->nodeValue;
	}

	/**
	 * Sets the subject of the currently opened OpenDocument file.
	 *
	 * @param subject The new subject to set for the document.
	 *
	 * @sa subject()
	 */
	public function setSubject($subject)
	{
		if($this->fileOpened == false)
		{
			trigger_error('Cannot set subject when no document is loaded');
			return;
		}

		$this->metaNodes['subject']->nodeValue = $subject;

		$this->setChanged();
	}

	/**
	 * Returns some keywords about the currently opened OpenDocument file.
	 *
	 * @return Some keywords about the document as given by the document's author.
	 *
	 * @sa setKeywords()
	 */
	public function keywords()
	{
		if($this->fileOpened == false)
		{
			trigger_error('Trying to get keywords when no document is loaded');
			return '';
		}

		return $this->metaNodes['keywords']->nodeValue;
	}

	/**
	 * Sets keywords about the currently opened OpenDocument file.
	 *
	 * @param keywords The new keywords to set for the document.
	 *
	 * @sa keywords()
	 */
	public function setKeywords($keywords)
	{
		if($this->fileOpened == false)
		{
			trigger_error('Cannot set keywords when no document is loaded');
			return;
		}

		$this->metaNodes['keywords']->nodeValue = $keywords;

		$this->setChanged();
	}

	/**
	 * Returns comments about the currently opened OpenDocument file.
	 *
	 * @return The comments about the document as given by the document's author.
	 *
	 * @sa setComments()
	 */
	public function comments()
	{
		if($this->fileOpened == false)
		{
			trigger_error('Trying to get comments when no document is loaded');
			return '';
		}

		return $this->metaNodes['comments']->nodeValue;
	}

	/**
	 * Sets comments about the currently opened OpenDocument file.
	 *
	 * @param comments The new comments to set for the document.
	 *
	 * @sa comments()
	 */
	public function setComments($comments)
	{
		if($this->fileOpened == false)
		{
			trigger_error('Cannot set comments when no document is loaded');
			return;
		}

		$this->metaNodes['comments']->nodeValue = $comments;

		$this->setChanged();
	}

	/**
	 * Returns the name of the author of the currently opened OpenDocument file.
	 *
	 * @return The name of the document's author.
	 *
	 * @sa setAuthor()
	 */
	public function author()
	{
		if($this->fileOpened == false)
		{
			trigger_error('Trying to get author when no document is loaded');
			return '';
		}

		return $this->metaNodes['author']->nodeValue;
	}

	/**
	 * Sets the name of the author of the currently opened OpenDocument file.
	 *
	 * @param author The new name to use as the document's author.
	 *
	 * @sa author()
	 */
	public function setAuthor($author)
	{
		if($this->fileOpened == false)
		{
			trigger_error('Cannot set author when no document is loaded');
			return;
		}

		$this->metaNodes['author']->nodeValue = $author;

		$this->setChanged();
	}

	/**
	 * Converts the currently opened OpenDocument file to XHTML.
	 *
	 * This function does not modify the original document.
	 *
	 * @param xhtmlDocument URI to the resulting XHTML document.
	 *
	 * @return @p true if the document was successfully converted, @p false
	 *         otherwise.
	 */
	public function convertToXHTML($xhtmlDocument)
	{
		if($this->fileOpened == false)
		{
			trigger_error('No document was loaded for conversion to XHTML');
			return false;
		}

		if(((URI::permissions(dirname($xhtmlDocument)) & PERMISSION_APPEND) &&
		    (URI::fileExists($xhtmlDocument) == false ||
		     URI::permissions($xhtmlDocument) & PERMISSION_MODIFY)) == false)
		{
			return false;
		}

		// copy images
		$images = glob("{$this->tmpdir}/Pictures/*");
		if($images !== false)
		{
			$picturesDir = dirname($xhtmlDocument).'/Pictures';
			if(URI::fileExists($picturesDir) == false)
			{
				URI::mkdir($picturesDir);
			}

			foreach($images as $image)
			{
				$basename = basename($image);
				if($basename == '.' || $basename == '..')
				{
					continue;
				}
				copy($image, "$picturesDir/$basename");
			}
		}

		// convert the OpenDocument XML file using the XSL stylesheets into an XHTML file
		OpenDocument2XHTML::reset();

		$stylesDoc = new DOMDocument();
		$contentDoc = new DOMDocument();
		if($stylesDoc->load("{$this->tmpdir}/styles.xml") == false ||
		   $contentDoc->load("{$this->tmpdir}/content.xml") == false)
		{
			return false;
		}

		$stylesXSL = new DOMDocument();
		$stylesXSL->load(AUKYLA_DIR.'/data/base/OpenDocument/Styles.xsl');
		$contentXSL = new DOMDocument();
		$contentXSL->load(AUKYLA_DIR.'/data/base/OpenDocument/Content2XHTML.xsl');

		$xsltProcessor = new XSLTProcessor();
		if($xsltProcessor->hasExsltSupport() == false)
		{
			die('EXSLT support in PHP is required for converting OpenDocument files!');
		}

		$xsltProcessor->registerPHPFunctions();
		$xsltProcessor->importStyleSheet($stylesXSL);
		$xsltProcessor->transformToXML($stylesDoc);

		$xsltProcessor = new XSLTProcessor();
		$xsltProcessor->registerPHPFunctions();
		$xsltProcessor->importStyleSheet($contentXSL);
		$xsltProcessor->setParameter('', 'imagePrefix', Config::globals('downloadURL').'?file='.dirname($xhtmlDocument).'/Pictures/');
		$xsltProcessor->setParameter('', 'title', $this->title());
		if($xsltProcessor->transformToURI($contentDoc, $xhtmlDocument) == false)
		{
			return false;
		}

		return true;
	}

	/**
	 * Scales all images in the currently opened OpenDocument file to a maximum
	 * resolution.
	 *
	 * @param maxWidth  The maximum width of the images.
	 * @param maxHeight The maximum height of the images.
	 *
	 * @return @p true on success, @p false on error.
	 *
	 * @note This function requires ImageMagick to be installed to be able to work.
	 */
	public function scaleImages($maxWidth, $maxHeight)
	{
		if($this->fileOpened == false)
		{
			trigger_error('No document was loaded for scaling images');
			return false;
		}

		if((URI::permissions($this->document) & PERMISSION_MODIFY) == 0)
		{
			return false;
		}

		$images = glob("{$this->tmpdir}/Pictures/*");
		if($images !== false && sizeof($images) > 0)
		{
			foreach($images as $image)
			{
				unset($output);
				exec("identify $image", $output, $return);
				if($return != 0)
				{
					trigger_error("Failed to run 'identify', is ImageMagick installed?");
					return false;
				}

				$properties = explode(' ', $output[0]);
				$geometry = $properties[2];
				list($width, $height) = explode('x', $geometry);
				if($width > $maxWidth || $height > $maxHeight)
				{
					exec("convert -size {$maxWidth}x{$maxHeight} $image -scale {$maxWidth}x{$maxHeight} $image", $output, $return);
					if($return != 0)
					{
						trigger_error("Failed to run 'convert', is ImageMagick installed?");
						return false;
					}
				}
			}

			$this->setChanged();
		}

		return true;
	}

	/**
	 * Returns the temporary directory in which you can find the unzipped
	 * version of a loaded OpenDocument file.
	 *
	 * If you change the contents of the temporary directory, be sure to
	 * call setChanged() so your changes will be saved.
	 *
	 * @return The path to the temporary directory where the OpenDocument
	 *         file has been unzipped to, or an empty string if no file was
	 *         loaded.
	 *
	 * @sa setChanged()
	 */
	public function temporaryDirectory()
	{
		if($this->fileOpened == false)
		{
			trigger_error('Requesting temporary directory with no document loaded');
			return '';
		}

		return $this->tmpdir;
	}

	private function loadMeta()
	{
		$this->metaNodes = array('title' => false,
		                         'subject' => false,
		                         'keywords' => false,
		                         'comments' => false,
		                         'author' => false);

		$this->metaDoc = new DOMDocument();
		if($this->metaDoc->load("{$this->tmpdir}/meta.xml") == false)
		{
			return false;
		}

		$dcNS     = 'http://purl.org/dc/elements/1.1/';
		$metaNS   = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0';
		$officeNS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0';

		$xpath = new DOMXPath($this->metaDoc);
		$xpath->registerNamespace('dc',     $dcNS);
		$xpath->registerNamespace('meta',   $metaNS);
		$xpath->registerNamespace('office', $officeNS);

		$metaNodes = $xpath->query('//office:document-meta/office:meta/*');
		foreach($metaNodes as $metaNode)
		{
			if($metaNode->namespaceURI == $dcNS)
			{
				if($metaNode->localName == 'title')
				{
					$this->metaNodes['title'] = $metaNode;
				}
				if($metaNode->localName == 'subject')
				{
					$this->metaNodes['subject'] = $metaNode;
				}
				if($metaNode->localName == 'description')
				{
					$this->metaNodes['comments'] = $metaNode;
				}
				if($metaNode->localName == 'author')
				{
					$this->metaNodes['creator'] = $metaNode;
				}
			}
			elseif($metaNode->namespaceURI == $metaNS)
			{
				if($metaNode->localName == 'keyword')
				{
					$this->metaNodes['keywords'] = $metaNode;
				}
			}
		}

		$officeMetaNode = $xpath->query('//office:document-meta/office:meta')->item(0);
		foreach(array('title',
		              'subject',
		              'keywords',
		              'comments',
		              'author') as $key)
		{
			if($this->metaNodes[$key] === false)
			{
				if($key == 'keywords')
				{
					$ns = $metaNS;
				}
				else
				{
					$ns = $dcNS;
				}

				$node = $this->metaDoc->createElementNS($ns, $key, '');
				$officeMetaNode->appendChild($node);
				$this->metaNodes[$key] = $node;
			}
		}
	}

	/**
	 * Marks the document as changed so it will be saved when you unload it.
	 * Call this function if you have changed the document directly.
	 *
	 * @sa unload()
	 */
	public function setChanged()
	{
		if($this->cached == true)
		{
			$simplifiedFilename = String::simplifyPath($this->document);
			self::$openedDocuments[$simplifiedFilename]->changed = true;
		}
		$this->changed = true;
	}

	/**
	 * Closes the currently opened OpenDocument file.
	 *
	 * @sa open()
	 */
	private function close()
	{
		if($this->cached == false)
		{
			if($this->changed == true)
			{
				$this->metaDoc->save("{$this->tmpdir}/meta.xml");

				if(URI::permissions($this->document) & PERMISSION_MODIFY)
				{
					exec("cd {$this->tmpdir}; rm document.od; zip -r - * > document.od", $output, $return);
					if($return == 0)
					{
						copy("{$this->tmpdir}/document.od", $this->document);
					}
				}
				else
				{
					trigger_error("Not allowed to save {$this->document}");
				}
			}

			// clean up
			exec("rm -R {$this->tmpdir}");

			$this->fileOpened = false;
			$this->document = '';
			unset($this->meta);
		}
	}

	private static $openedDocuments = array();
	private $fileOpened = false;
	private $document = '';
	private $tmpdir = '';
	private $metaDoc;
	private $metaNodes;
	private $changed = false;
	private $cached = false;
}

/**
 * @internal
 */
class OpenDocument2XHTML
{
	public static function reset()
	{
		self::$styles = array();
		self::$fontFaces = array();
		self::$listStyles = array();
	}

	/**
	 * @internal
	 */
	public static function setFontFace($name, $family)
	{
		self::$fontFaces[$name] = $family;

		return '';
	}

	/**
	 * @internal
	 */
	public static function fontFaceFamily($name)
	{
		return (isset(self::$fontFaces[$name]) ? self::$fontFaces[$name] : '');
	}

	/**
	 * @internal
	 */
	public static function createStyle($family, $styleName, $parentStyleName = '')
	{
		if(isset(self::$styles[$family]) == false)
		{
			self::$styles[$family] = array();
		}
		if(isset(self::$styles[$family][$styleName]) == false)
		{
			if(isset(self::$styles[$family][$parentStyleName]))
			{
				self::$styles[$family][$styleName] = self::$styles[$family][$parentStyleName];
			}
			else
			{
				self::$styles[$family][$styleName] = array();
				if($family == 'paragraph')
				{
					self::$styles[$family][$styleName]['margin-top'] = '0cm';
					self::$styles[$family][$styleName]['margin-bottom'] = '0cm';
				}
				elseif($family == 'paragraph')
				{
					self::$styles[$family][$styleName]['margin'] = '0cm';
					self::$styles[$family][$styleName]['padding'] = '0cm';
				}
			}
		}

		return '';
	}

	/**
	 * @internal
	 */
	public static function createListStyle($styleName)
	{
		if(isset(self::$listStyles[$styleName]) == false)
		{
			self::$listStyles[$styleName] = array();
		}

		return '';
	}

	/**
	 * @internal
	 */
	public static function setStyleTextProperties($family,
	                                              $styleName,
	                                              $fontName,
	                                              $fontSize,
	                                              $fontStyle,
	                                              $fontWeight,
	                                              $textUnderlineStyle,
	                                              $color,
	                                              $backgroundColor)
	{
		self::createStyle($family, $styleName);

		self::$styles[$family][$styleName] = array_merge(self::$styles[$family][$styleName],
		                                                 self::cssStyleTextProperties($fontName,
		                                                                              $fontSize,
		                                                                              $fontStyle,
		                                                                              $fontWeight,
		                                                                              $textUnderlineStyle,
		                                                                              $color,
		                                                                              $backgroundColor));

		return '';
	}

	/**
	 * @internal
	 */
	public static function setStyleParagraphProperties($family,
	                                                   $styleName,
	                                                   $marginTop,
	                                                   $marginBottom,
	                                                   $textAlign)
	{
		self::createStyle($family, $styleName);

		self::$styles[$family][$styleName] = array_merge(self::$styles[$family][$styleName],
		                                                 self::cssStyleParagraphProperties($marginTop,
		                                                                                   $marginBottom,
		                                                                                   $textAlign));

		return '';
	}

	/**
	 * @internal
	 */
	public static function setStyleTableProperties($family,
	                                               $styleName,
	                                               $width)
	{
		self::createStyle($family, $styleName);

		self::$styles[$family][$styleName] = array_merge(self::$styles[$family][$styleName],
		                                                 self::cssStyleTableProperties($width));

		return '';
	}

	/**
	 * @internal
	 */
	public static function setStyleTableCellProperties($family,
	                                                   $styleName,
	                                                   $border,
	                                                   $borderTop,
	                                                   $borderRight,
	                                                   $borderBottom,
	                                                   $borderLeft,
	                                                   $backgroundColor,
	                                                   $padding)
	{
		self::createStyle($family, $styleName);

		self::$styles[$family][$styleName] = array_merge(self::$styles[$family][$styleName],
		                                                 self::cssStyleTableCellProperties($border,
		                                                                                   $borderTop,
		                                                                                   $borderRight,
		                                                                                   $borderBottom,
		                                                                                   $borderLeft,
		                                                                                   $backgroundColor,
		                                                                                   $padding));

		return '';
	}

	/**
	 * @internal
	 */
	public static function setStyleListProperties($styleName,
	                                              $listLevel,
	                                              $bulletChar,
	                                              $numFormat)
	{
		self::createListStyle($styleName);

		self::$listStyles[$styleName][$listLevel] = self::cssStyleListProperties($bulletChar,
		                                                                         $numFormat);

		return '';
	}

	/**
	 * @internal
	 */
	public static function style($family, $styleName)
	{
		$styleArray = (isset(self::$styles[$family][$styleName]) ? self::$styles[$family][$styleName] :
		               (isset(self::$styles[$family]['']) ? self::$styles[$family][''] : array()));

		$style = '';
		foreach($styleArray as $key => $value)
		{
			if($style != '')
			{
				$style .= '; ';
			}
			$style .= "$key: $value";
		}

		return $style;
	}

	/**
	 * @internal
	 */
	public static function listStyle($styleName, $listLevel)
	{
		$styleArray = (isset(self::$listStyles[$styleName][$listLevel]) ?
		               self::$listStyles[$styleName][$listLevel] : array());

		$style = '';
		foreach($styleArray as $key => $value)
		{
			if($style != '')
			{
				$style .= '; ';
			}
			$style .= "$key: $value";
		}

		return $style;
	}

	/**
	 * @internal
	 */
	private static function cssStyleTextProperties($fontName,
	                                               $fontSize,
	                                               $fontStyle,
	                                               $fontWeight,
	                                               $textUnderlineStyle,
	                                               $color,
	                                               $backgroundColor)
	{
		$style = array();

		if($fontName != '')
		{
			// fonts families are ignored purposefully, with the exception of
			// seperating monospace/non-monospace
			//$style['font-family'] = self::fontFaceFamily($fontName);
			$font = self::fontFaceFamily($fontName);
			if(strstr($font, 'Courier') !== false ||
			   strstr($font, 'Cumberland') !== false)
			{
				$style['font-family'] = 'Courier New';
			}
		}
		if($fontSize != '')
		{
			// font sizes are ignored purposefully
			//$style['font-size'] = $fontSize;
		}
		if($fontStyle != '')
		{
			$style['font-style'] = $fontStyle;
		}
		if($fontWeight != '')
		{
			$style['font-weight'] = $fontWeight;
		}
		if($textUnderlineStyle != '')
		{
			$style['text-decoration'] = 'underline';
		}
		if($color != '')
		{
			$style['color'] = $color;
		}
		if($backgroundColor != '')
		{
			$style['background-color'] = $backgroundColor;
		}

		return $style;
	}

	/**
	 * @internal
	 */
	private static function cssStyleParagraphProperties($marginTop,
	                                                    $marginBottom,
	                                                    $textAlign)
	{
		$style = array();

		if($marginTop != '')
		{
			$style['margin-top'] = $marginTop;
		}
		if($marginBottom != '')
		{
			$style['margin-bottom'] = $marginBottom;
		}
		if($textAlign != '')
		{
			$style['text-align'] = $textAlign;
		}

		return $style;
	}

	/**
	 * @internal
	 */
	private static function cssStyleTableProperties($width)
	{
		$style = array();

		if($width != '')
		{
			$style['width'] = $width;
		}

		return $style;
	}

	/**
	 * @internal
	 */
	private static function cssStyleTableCellProperties($border,
	                                                    $borderTop,
	                                                    $borderRight,
	                                                    $borderBottom,
	                                                    $borderLeft,
	                                                    $backgroundColor,
	                                                    $padding)
	{
		$style = array();

		if($border != '')
		{
			$style['border'] = $border;
		}
		if($borderTop != '')
		{
			$style['border-top'] = $borderTop;
		}
		if($borderRight != '')
		{
			$style['border-right'] = $borderRight;
		}
		if($borderBottom != '')
		{
			$style['border-bottom'] = $borderBottom;
		}
		if($borderLeft != '')
		{
			$style['border-left'] = $borderLeft;
		}
		if($backgroundColor != '')
		{
			$style['background-color'] = $backgroundColor;
		}
		if($padding != '')
		{
			$style['padding'] = $padding;
		}

		return $style;
	}

	/**
	 * @internal
	 */
	private static function cssStyleListProperties($bulletChar,
	                                               $numFormat)
	{
		$style = array();

		if($bulletChar != '')
		{
			if($bulletChar == '•')
			{
				$style['list-style'] = 'disc';
			}
			elseif($bulletChar == '')
			{
				$style['list-style'] = 'square';
			}
		}
		else
		{
			if($numFormat == '1')
			{
				$style['list-style'] = 'decimal';
			}
			elseif($numFormat == 'a')
			{
				$style['list-style'] = 'lower-latin';
			}
			elseif($numFormat == 'A')
			{
				$style['list-style'] = 'upper-latin';
			}
			elseif($numFormat == 'i')
			{
				$style['list-style'] = 'lower-roman';
			}
			elseif($numFormat == 'I')
			{
				$style['list-style'] = 'upper-roman';
			}
		}

		return $style;
	}

	private static $styles;
	private static $fontFaces;
	private static $listStyles;
}

?>
Return current item: Aukyla Platform