diff --git a/doc/class-apemsel.AttributedString.AttributedString.html b/doc/class-apemsel.AttributedString.AttributedString.html index 7a7c494..d3f642b 100644 --- a/doc/class-apemsel.AttributedString.AttributedString.html +++ b/doc/class-apemsel.AttributedString.AttributedString.html @@ -124,7 +124,7 @@

Direct known subclasses

Author: Adrian Pemsel apemsel@gmail.com
- Located at AttributedString.php + Located at AttributedString.php
@@ -694,7 +694,7 @@

Returns

# - toHtml( string $tag = "span", string $classPrefix = "" ) + toHtml( string $tag = "span", string $classPrefix = "" )

Convert to HTML, using a given class to mark attribute spans

@@ -717,10 +717,6 @@

Returns

string
HTML
-

Throws

-
- Exception
if the AttributedString cannot be converted to HTML due to improper nesting -
@@ -738,7 +734,7 @@

Throws

# - combineAttributes( string $op, string $attribute1, string $attribute2 = false, string $to = false ) + combineAttributes( string $op, string $attribute1, string $attribute2 = false, string $to = false )

Combine attributes with the given boolean operation

@@ -768,6 +764,44 @@

Throws

+
+
+ + + + + public + + + + + +
+ # + attributeToString( string $attribute, string $true = "-", string $false = " " ) + +
+

Convert attribute map to a visual string representation (e.g. for debugging)

+
+ +
@@ -782,7 +816,7 @@

Throws

# - enablebyteToCharCache( ) + enablebyteToCharCache( )

Enable and fill cache for byte to char offset conversion

@@ -813,7 +847,7 @@

Throws

# - byteToCharOffset( $boff ) + byteToCharOffset( $boff )
@@ -842,7 +876,7 @@

Throws

# - charToByteOffset( $char ) + charToByteOffset( $char )
@@ -871,7 +905,7 @@

Throws

# - byteToCharOffsetString( $string, $boff ) + byteToCharOffsetString( $string, $boff )
@@ -900,7 +934,7 @@

Throws

# - utf8CharLen( $byte ) + utf8CharLen( $byte )
@@ -929,7 +963,7 @@

Throws

# - count( ) + count( )

Return string length (number of UTF-8 chars, not strlen())

diff --git a/doc/class-apemsel.AttributedString.MutableAttributedString.html b/doc/class-apemsel.AttributedString.MutableAttributedString.html index afdc575..3e8396c 100644 --- a/doc/class-apemsel.AttributedString.MutableAttributedString.html +++ b/doc/class-apemsel.AttributedString.MutableAttributedString.html @@ -261,6 +261,7 @@

See

__construct(), __toString(), + attributeToString(), attributesAt(), byteToCharOffset(), byteToCharOffsetString(), diff --git a/doc/class-apemsel.AttributedString.TokenizedAttributedString.html b/doc/class-apemsel.AttributedString.TokenizedAttributedString.html index 88e808f..808e60d 100644 --- a/doc/class-apemsel.AttributedString.TokenizedAttributedString.html +++ b/doc/class-apemsel.AttributedString.TokenizedAttributedString.html @@ -653,6 +653,7 @@

Returns

__toString(), + attributeToString(), attributesAt(), byteToCharOffset(), byteToCharOffsetString(), diff --git a/doc/source-class-apemsel.AttributedString.AttributedString.html b/doc/source-class-apemsel.AttributedString.AttributedString.html index a4a80f3..601a2c0 100644 --- a/doc/source-class-apemsel.AttributedString.AttributedString.html +++ b/doc/source-class-apemsel.AttributedString.AttributedString.html @@ -352,175 +352,201 @@

Classes

268 * @param string $tag HTML tag to use for the spans (defaults is "<span>") 269 * @param string $classPrefix Optional prefix used to convert the attribute names to class names 270 * @return string HTML -271 * @throws Exception if the AttributedString cannot be converted to HTML due to improper nesting -272 */ -273 public function toHtml($tag = "span", $classPrefix = "") { -274 foreach($this->attributes as $attribute => $map) $state[$attribute] = false; -275 -276 $html = ""; -277 $stack = []; -278 $lastPos = 0; -279 -280 for ($i=0; $i<$this->length; $i++) -281 { -282 foreach($this->attributes as $attribute => &$map) -283 { -284 if ($this->attributes[$attribute][$i] != $state[$attribute]) -285 { -286 $state[$attribute] = $this->attributes[$attribute][$i]; -287 -288 $html .= mb_substr($this->string, $lastPos, $i-$lastPos, "utf-8"); -289 $lastPos = $i; -290 -291 if ($state[$attribute]) -292 { -293 $html .= "<$tag class=\"$classPrefix$attribute\">"; -294 $stack[] = $attribute; -295 } -296 else -297 { -298 if ($attribute != array_pop($stack)) -299 { -300 throw new Exception("Attributes are not properly nested for HTML conversion"); -301 } -302 $html .= "</$tag>"; -303 } -304 } -305 } -306 } -307 -308 $html .= mb_substr($this->string, $lastPos, $this->length-$lastPos, 'utf-8'); -309 -310 // Close all spans that remained open -311 $html .= str_repeat("</$tag>", count($stack)); -312 -313 return $html; -314 } -315 -316 /** -317 * Combine attributes with the given boolean operation -318 * -319 * @param string $op one of or|xor|and|not -320 * @param string $attribute1 name of the first attribute -321 * @param string $attribute2 Name of the second attribute. Ignored for "not" operation. -322 * @param string $to optional name of the attribute to copy the result to -323 * @throws InvalidArgumentException if one of the attributes does not exist or an unkown operation is given -324 */ -325 public function combineAttributes($op, $attribute1, $attribute2 = false, $to = false) -326 { -327 $to = isset($to) ? $to : $attribute1; -328 $op = strtolower($op); -329 -330 if ($op == "not") { -331 $attribute2 = $attribute1; -332 } -333 -334 if (!$this->hasAttribute($attribute1) or !$this->hasAttribute($attribute2)) { -335 throw new \InvalidArgumentException("Attribute does not exist"); -336 } -337 -338 if (!isset($this->attributes[$to])) { -339 $this->attributes[$to] = []; // No need to init because array is created below -340 } -341 -342 // Switch outside the loops for speed -343 switch ($op) { -344 case 'or': -345 for($i = 0; $i < $this->length; $i++) { -346 $this->attributes[$to][$i] = $this->attributes[$attribute1][$i] || $this->attributes[$attribute2][$i]; -347 } -348 break; -349 -350 case 'xor': -351 for($i = 0; $i < $this->length; $i++) { -352 $this->attributes[$to][$i] = ($this->attributes[$attribute1][$i] xor $this->attributes[$attribute2][$i]); -353 } -354 break; -355 -356 case 'and': -357 for($i = 0; $i < $this->length; $i++) { -358 $this->attributes[$to][$i] = $this->attributes[$attribute1][$i] && $this->attributes[$attribute2][$i]; -359 } -360 break; -361 -362 case 'not': -363 for($i = 0; $i < $this->length; $i++) { -364 $this->attributes[$to][$i] = !$this->attributes[$attribute1][$i]; -365 } -366 break; -367 -368 default: -369 throw new \InvalidArgumentException("Unknown operation"); -370 } -371 } -372 -373 /** -374 * Enable and fill cache for byte to char offset conversion -375 * -376 * May improve performance if setPattern is used extensively -377 */ -378 public function enablebyteToCharCache() { -379 $this->byteToChar = []; -380 $char = 0; -381 for ($i = 0; $i < strlen($this->string); ) { -382 $char++; -383 $byte = $this->string[$i]; -384 $cl = self::utf8CharLen($byte); -385 $i += $cl; -386 -387 $this->byteToChar[$i] = $char; -388 } -389 } -390 -391 protected function byteToCharOffset($boff) { -392 if (isset($this->byteToChar[$boff])) return $this->byteToChar[$boff]; +271 */ +272 public function toHtml($tag = "span", $classPrefix = "") { +273 foreach($this->attributes as $attribute => $map) $state[$attribute] = false; +274 +275 $html = ""; +276 $stack = []; +277 $lastPos = 0; +278 +279 for ($i=0; $i<$this->length; $i++) +280 { +281 foreach($this->attributes as $attribute => &$map) +282 { +283 if ($this->attributes[$attribute][$i] != $state[$attribute]) +284 { +285 $state[$attribute] = $this->attributes[$attribute][$i]; +286 +287 $html .= mb_substr($this->string, $lastPos, $i-$lastPos, "utf-8"); +288 $lastPos = $i; +289 +290 if ($state[$attribute]) +291 { +292 $html .= "<$tag class=\"$classPrefix$attribute\">"; +293 $stack[] = $attribute; +294 } +295 else +296 { +297 // Close attribute span. If the top of the stack does not equal the attribute to be closed +298 // close, pop and stash it. This happens when span a ends in span b. +299 $stashed = []; +300 while($open = array_pop($stack)) +301 { +302 $html .= "</$tag>"; +303 if ($attribute == $open) { +304 break; +305 } +306 $stashed[] = $open; +307 } +308 +309 // Now repopen the stashed spans and put them back on the stack. +310 foreach($stashed as $a) { +311 $stack[] = $a; +312 $html .= "<$tag class=\"$classPrefix$a\">"; +313 } +314 } +315 } +316 } +317 } +318 +319 $html .= mb_substr($this->string, $lastPos, $this->length-$lastPos, 'utf-8'); +320 +321 // Close all spans that remained open +322 $html .= str_repeat("</$tag>", count($stack)); +323 +324 return $html; +325 } +326 +327 /** +328 * Combine attributes with the given boolean operation +329 * +330 * @param string $op one of or|xor|and|not +331 * @param string $attribute1 name of the first attribute +332 * @param string $attribute2 Name of the second attribute. Ignored for "not" operation. +333 * @param string $to optional name of the attribute to copy the result to +334 * @throws InvalidArgumentException if one of the attributes does not exist or an unkown operation is given +335 */ +336 public function combineAttributes($op, $attribute1, $attribute2 = false, $to = false) +337 { +338 $to = isset($to) ? $to : $attribute1; +339 $op = strtolower($op); +340 +341 if ($op == "not") { +342 $attribute2 = $attribute1; +343 } +344 +345 if (!$this->hasAttribute($attribute1) or !$this->hasAttribute($attribute2)) { +346 throw new \InvalidArgumentException("Attribute does not exist"); +347 } +348 +349 if (!isset($this->attributes[$to])) { +350 $this->attributes[$to] = []; // No need to init because array is created below +351 } +352 +353 // Switch outside the loops for speed +354 switch ($op) { +355 case 'or': +356 for($i = 0; $i < $this->length; $i++) { +357 $this->attributes[$to][$i] = $this->attributes[$attribute1][$i] || $this->attributes[$attribute2][$i]; +358 } +359 break; +360 +361 case 'xor': +362 for($i = 0; $i < $this->length; $i++) { +363 $this->attributes[$to][$i] = ($this->attributes[$attribute1][$i] xor $this->attributes[$attribute2][$i]); +364 } +365 break; +366 +367 case 'and': +368 for($i = 0; $i < $this->length; $i++) { +369 $this->attributes[$to][$i] = $this->attributes[$attribute1][$i] && $this->attributes[$attribute2][$i]; +370 } +371 break; +372 +373 case 'not': +374 for($i = 0; $i < $this->length; $i++) { +375 $this->attributes[$to][$i] = !$this->attributes[$attribute1][$i]; +376 } +377 break; +378 +379 default: +380 throw new \InvalidArgumentException("Unknown operation"); +381 } +382 } +383 +384 /** +385 * Convert attribute map to a visual string representation (e.g. for debugging) +386 * +387 * @param string $attribute name of the attribute +388 * @param string $true char to use for true state of attribute +389 * @param string $false char to use for false state of attribute +390 */ +391 public function attributeToString($attribute, $true = "-", $false = " ") { +392 $map = $this->attributes[$attribute]; 393 -394 return $this->byteToChar[$boff] = self::byteToCharOffsetString($this->string, $boff); -395 } -396 -397 protected function charToByteOffset($char) { -398 $byte = strlen(mb_substr($this->string, 0, $char, "utf-8")); -399 if (!isset($this->byteToChar[$byte])) $this->byteToChar[$byte] = $char; -400 -401 return $byte; -402 } -403 -404 protected static function byteToCharOffsetString($string, $boff) { -405 $result = 0; -406 -407 for ($i = 0; $i < $boff; ) { -408 $result++; -409 $byte = $string[$i]; +394 return implode("", array_map(function($v) use ($true, $false) { +395 return $v ? $true : $false; +396 }, $map)); +397 } +398 +399 /** +400 * Enable and fill cache for byte to char offset conversion +401 * +402 * May improve performance if setPattern is used extensively +403 */ +404 public function enablebyteToCharCache() { +405 $this->byteToChar = []; +406 $char = 0; +407 for ($i = 0; $i < strlen($this->string); ) { +408 $char++; +409 $byte = $this->string[$i]; 410 $cl = self::utf8CharLen($byte); 411 $i += $cl; -412 } -413 -414 return $result; +412 +413 $this->byteToChar[$i] = $char; +414 } 415 } 416 -417 protected static function utf8CharLen($byte) { -418 $base2 = str_pad(base_convert((string) ord($byte), 10, 2), 8, "0", STR_PAD_LEFT); -419 $p = strpos($base2, "0"); -420 -421 if ($p == 0) { -422 return 1; -423 } elseif ($p <= 4) { -424 return $p; -425 } else { -426 throw new \InvalidArgumentException(); -427 } +417 protected function byteToCharOffset($boff) { +418 if (isset($this->byteToChar[$boff])) return $this->byteToChar[$boff]; +419 +420 return $this->byteToChar[$boff] = self::byteToCharOffsetString($this->string, $boff); +421 } +422 +423 protected function charToByteOffset($char) { +424 $byte = strlen(mb_substr($this->string, 0, $char, "utf-8")); +425 if (!isset($this->byteToChar[$byte])) $this->byteToChar[$byte] = $char; +426 +427 return $byte; 428 } 429 -430 /** -431 * Return string length (number of UTF-8 chars, not strlen()) -432 * -433 * @return int string length -434 */ -435 public function count() { -436 return $this->length; -437 } -438 } -439 +430 protected static function byteToCharOffsetString($string, $boff) { +431 $result = 0; +432 +433 for ($i = 0; $i < $boff; ) { +434 $result++; +435 $byte = $string[$i]; +436 $cl = self::utf8CharLen($byte); +437 $i += $cl; +438 } +439 +440 return $result; +441 } +442 +443 protected static function utf8CharLen($byte) { +444 $base2 = str_pad(base_convert((string) ord($byte), 10, 2), 8, "0", STR_PAD_LEFT); +445 $p = strpos($base2, "0"); +446 +447 if ($p == 0) { +448 return 1; +449 } elseif ($p <= 4) { +450 return $p; +451 } else { +452 throw new \InvalidArgumentException(); +453 } +454 } +455 +456 /** +457 * Return string length (number of UTF-8 chars, not strlen()) +458 * +459 * @return int string length +460 */ +461 public function count() { +462 return $this->length; +463 } +464 } +465