1<?php 2declare( strict_types = 1 ); 3 4namespace Wikimedia\Parsoid\Ext\Cite; 5 6use DOMElement; 7use stdClass; 8use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI; 9 10class ReferencesData { 11 12 /** 13 * @var int 14 */ 15 private $index; 16 17 /** 18 * @var RefGroup[] 19 */ 20 private $refGroups; 21 22 /** 23 * ReferencesData constructor. 24 */ 25 public function __construct() { 26 $this->index = 0; 27 $this->refGroups = []; 28 } 29 30 /** 31 * @param string $groupName 32 * @param bool $allocIfMissing 33 * @return RefGroup|null 34 */ 35 public function getRefGroup( string $groupName = '', bool $allocIfMissing = false ): ?RefGroup { 36 if ( !isset( $this->refGroups[$groupName] ) && $allocIfMissing ) { 37 $this->refGroups[$groupName] = new RefGroup( $groupName ); 38 } 39 return $this->refGroups[$groupName] ?? null; 40 } 41 42 /** 43 * @param string|null $groupName 44 */ 45 public function removeRefGroup( ?string $groupName = null ): void { 46 if ( $groupName !== null ) { 47 // '' is a valid group (the default group) 48 unset( $this->refGroups[$groupName] ); 49 } 50 } 51 52 /** 53 * @param ParsoidExtensionAPI $extApi 54 * @param string $groupName 55 * @param string $refName 56 * @param string $about 57 * @param bool $skipLinkback 58 * @param DOMElement $linkBack 59 * @return stdClass 60 */ 61 public function add( 62 ParsoidExtensionAPI $extApi, string $groupName, string $refName, 63 string $about, bool $skipLinkback, DOMElement $linkBack 64 ): stdClass { 65 $group = $this->getRefGroup( $groupName, true ); 66 // Looks like Cite.php doesn't try to fix ids that already have 67 // a "_" in them. Ex: name="a b" and name="a_b" are considered 68 // identical. Not sure if this is a feature or a bug. 69 // It also considers entities equal to their encoding 70 // (i.e. '&' === '&'), which is done: 71 // in PHP: Sanitizer#decodeTagAttributes and 72 // in Parsoid: ExtensionHandler#normalizeExtOptions 73 $refName = $extApi->sanitizeHTMLId( $refName ); 74 $hasRefName = strlen( $refName ) > 0; 75 76 if ( $hasRefName && isset( $group->indexByName[$refName] ) ) { 77 $ref = $group->indexByName[$refName]; 78 if ( $ref->contentId && !$ref->hasMultiples ) { 79 $ref->hasMultiples = true; 80 // Use the non-pp version here since we've already stored attribs 81 // before putting them in the map. 82 $ref->cachedHtml = $extApi->getContentHTML( $ref->contentId ); 83 } 84 $ref->nodes[] = $linkBack; 85 } else { 86 // The ids produced Cite.php have some particulars: 87 // Simple refs get 'cite_ref-' + index 88 // Refs with names get 'cite_ref-' + name + '_' + index + (backlink num || 0) 89 // Notes (references) whose ref doesn't have a name are 'cite_note-' + index 90 // Notes whose ref has a name are 'cite_note-' + name + '-' + index 91 $n = $this->index; 92 $refKey = strval( 1 + $n ); 93 $refIdBase = 'cite_ref-' . ( $hasRefName ? $refName . '_' . $refKey : $refKey ); 94 $noteId = 'cite_note-' . ( $hasRefName ? $refName . '-' . $refKey : $refKey ); 95 96 // bump index 97 $this->index += 1; 98 99 $ref = (object)[ 100 'about' => $about, 101 'contentId' => null, 102 'dir' => '', 103 'group' => $group->name, 104 'groupIndex' => count( $group->refs ) + 1, 105 'index' => $n, 106 'key' => $refIdBase, 107 'id' => $hasRefName ? $refIdBase . '-0' : $refIdBase, 108 'linkbacks' => [], 109 'name' => $refName, 110 'target' => $noteId, 111 'hasMultiples' => false, 112 // Just used for comparison when we have multiples 113 'cachedHtml' => '', 114 'nodes' => [], 115 ]; 116 $group->refs[] = $ref; 117 if ( $hasRefName ) { 118 $group->indexByName[$refName] = $ref; 119 $ref->nodes[] = $linkBack; 120 } 121 } 122 123 if ( !$skipLinkback ) { 124 $ref->linkbacks[] = $ref->key . '-' . count( $ref->linkbacks ); 125 } 126 127 return $ref; 128 } 129 130 /** 131 * @return RefGroup[] 132 */ 133 public function getRefGroups(): array { 134 return $this->refGroups; 135 } 136} 137