cmc-sales/php/app/vendors/description_formatter.php

215 lines
6.3 KiB
PHP

<?php
/**
* DescriptionFormatter: Converts plain text product descriptions to formatted HTML
* This ensures rich text formatting (bold, italics, lists) is properly supported
* in PDF generation.
*/
class DescriptionFormatter {
/**
* Convert plain text description to HTML with formatting
*
* Handles:
* - Bold formatting for key labels (Item Code:, Type:, etc.)
* - Ordered lists (lines starting with "1. ", "2. ", etc.)
* - Italic formatting for specific phrases
* - Proper HTML entity escaping for special characters
*
* @param string $text Plain text description
* @return string HTML-formatted description
*/
public static function format($text) {
if (empty($text)) {
return '';
}
// Escape HTML special characters that aren't part of our formatting
// We need to do this carefully to preserve our HTML tags
$text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
// Split into lines for processing
$lines = explode("\n", $text);
$result = array();
$inOrderedList = false;
$listItems = array();
foreach ($lines as $line) {
$trimmed = trim($line);
if (empty($trimmed)) {
// Flush any pending list before adding empty line
if ($inOrderedList && count($listItems) > 0) {
$result[] = self::formatOrderedList($listItems);
$listItems = array();
$inOrderedList = false;
}
$result[] = '';
continue;
}
// Check if this is an ordered list item (starts with "1. ", "2. ", etc.)
if (self::isOrderedListItem($trimmed)) {
if (!$inOrderedList) {
$inOrderedList = true;
$listItems = array();
}
// Extract text after the number
$listItems[] = self::extractListItemText($trimmed);
} else {
// Flush any pending list before processing non-list line
if ($inOrderedList && count($listItems) > 0) {
$result[] = self::formatOrderedList($listItems);
$listItems = array();
$inOrderedList = false;
}
// Process regular line
$formatted = self::formatLine($trimmed);
$result[] = $formatted;
}
}
// Flush any remaining list
if ($inOrderedList && count($listItems) > 0) {
$result[] = self::formatOrderedList($listItems);
}
// Join with line breaks
$html = implode('<br>', $result);
// Clean up excessive line breaks
$html = preg_replace('/<br><br><br>+/', '<br><br>', $html);
return $html;
}
/**
* Check if a line is an ordered list item (e.g., "1. text")
*/
private static function isOrderedListItem($line) {
return preg_match('/^\d+\.\s+/', $line) === 1;
}
/**
* Extract the text part of a list item, removing the number
*/
private static function extractListItemText($line) {
$matches = array();
if (preg_match('/^\d+\.\s+(.*)$/', $line, $matches)) {
return isset($matches[1]) ? $matches[1] : $line;
}
return $line;
}
/**
* Convert an array of list items to HTML ordered list
*/
private static function formatOrderedList($items) {
if (count($items) === 0) {
return '';
}
$html = '<ol>';
foreach ($items as $item) {
$html .= '<li>' . $item . '</li>';
}
$html .= '</ol>';
return $html;
}
/**
* Format a single line - mainly applying bold to key labels
*/
private static function formatLine($line) {
// Match "Key: value" pattern
if (preg_match('/^([^:]+):\s*(.*)$/', $line, $matches)) {
$key = $matches[1];
$value = isset($matches[2]) ? $matches[2] : '';
// Check if this key should be bolded
if (self::shouldBoldKey($key)) {
$line = '<strong>' . htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . ':</strong> ' . $value;
}
}
// Apply italics to specific phrases
$line = self::applyItalics($line);
return $line;
}
/**
* Determine if a key should be bolded (heuristic)
*/
private static function shouldBoldKey($key) {
$key = trim($key);
// List of known keys that should be bolded
$knownKeys = array(
'Item Code' => true,
'Item Description' => true,
'Type' => true,
'Cable Length' => true,
'Ui' => true,
'li' => true,
'Li, Ci' => true,
'II 2G' => true,
'II 2D' => true,
'IBEx' => true,
'Includes' => true,
'With standard' => true,
'See attached' => true,
'Testing at' => true,
);
// Check exact matches
if (isset($knownKeys[$key])) {
return true;
}
// Check partial matches for common patterns
$lowerKey = strtolower($key);
$commonPatterns = array(
'code',
'description',
'type',
'cable',
'temperature',
'pressure',
'includes',
'testing',
);
foreach ($commonPatterns as $pattern) {
if (strpos($lowerKey, $pattern) !== false) {
return true;
}
}
return false;
}
/**
* Apply italic formatting to specific phrases
*/
private static function applyItalics($line) {
$italicPatterns = array(
'See attached EC Conformity Declaration for the SE Sensor',
'If Insulated panels are used',
);
foreach ($italicPatterns as $pattern) {
if (strpos($line, $pattern) !== false) {
$escapedPattern = htmlspecialchars($pattern, ENT_QUOTES, 'UTF-8');
$line = str_replace($escapedPattern, '<em>' . $escapedPattern . '</em>', $line);
}
}
return $line;
}
}
?>