Grima 2018-08
Whispering into Alma's ear
MARC21.php
Go to the documentation of this file.
1<?php
2
3require_once "ISO2709.php";
4
6 public function __construct( $input ) {
7 parent::__construct( $input );
8 $this->prepend = fread( $this->input, 5 );
9 if( ctype_digit( $this->prepend ) ) $this->RecordTerminator = ISO2709::RecordTerminator;
10 else if( $this->prepend === "=LDR " ) {
11 $this->RecordTerminator = "\r\n\r\n";
12 } else if( $this->prepend === "=000 " ) {
13 $this->RecordTerminator = "\r\n\r\n";
14 }
15 $this->recordClass = "MARC21Record";
16 }
17}
18
20 public function __construct( $input ) {
21 parent::__construct( $input );
22 $this->recordClass = "MARC21Record";
23 }
24}
25
27 protected $mnemonicType;
28 function __construct( ) {
29 parent::__construct();
30 $this->set_default( "identifierLength", 2 );
31 $this->set_default( "indicatorLength", 2 );
32 $this->set_default( "lengthOfLengthOfField", 4 );
33 $this->set_default( "lengthOfStartingCharacterPosition", 5 );
34 $this->set_default( "lengthOfImplementationDefined", 0 );
35 $this->mnemonicType = "LC";
36 }
37
38 function isTagValid( $tag ) {
39 return is_string( $tag ) and ctype_digit( $tag ) and strlen( $tag ) == 3 and $tag !== "000";
40 }
41
42 function assertTagValid( $tag ) {
43 if( !self::isTagValid( $tag ) ) {
44 $this->exception("MARC21: Tags must be numeric and must be 3 bytes long. Thus '$tag' is not allowed.");
45 return false;
46 }
47 return parent::assertTagValid( $tag );
48 }
49
50 function isTagRepeatable( $tag ) {
51 return $tag != "001" and $tag != "005";
52 }
53
54 // 00* ordered ASCII-wise, otherwise order by century
55 function tagOrder( $a, $b ) {
56 if(!parent::isTagValid($a) || !parent::isTagValid($b)) return 0;
57 if( $a === $b ) return 0;
58 $a0 = substr( $a, 0, 2 );
59 $b0 = substr( $b, 0, 2 );
60 if( $a0 === $b0 and $a0 === "00" ) return strcmp( $a, $b );
61 if( $a0 === "00" ) { return -1; }
62 if( $b0 === "00" ) { return 1; }
63 $a0 = substr( $a, 0, 1 );
64 $b0 = substr( $b, 0, 1 );
65 if( $a0 === "0" and $b0 === "0" ) {
66 if( $a === "049" ) return 1;
67 if( $b === "049" ) return -1;
68 }
69 return strcmp( $a0, $b0 );
70 }
71
72 function assertTagOrder( $a, $b ) {
73 if( self::tagOrder( $a, $b ) > 0 ) {
74 $this->exception("MARC21: 00* is ordered by ASCII, and the rest by century. Thus '$a' should not precede '$b'.");
75 return false;
76 }
77 return parent::assertTagOrder( $a, $b );
78 }
79
80 function loadFromBinaryString( $string, $fuzzy = true ) {
81 $ret = parent::loadFromBinaryString( $string );
82 if( $ret === false ) return $ret;
83 if( strlen($this->leader) != 24 ) return false;
84 if(!ctype_print($this->leader))
85 $this->exception("MARC21: Leader can only contain 0x20 through 0x7E, not 0x" . bin2hex( $this->leader ) );
86 if($this->leader[23] != "0")
87 $this->exception("MARC21: Last character of leader is supposed to be '0' not '{$this->leader[23]}'");
88 return true;
89 // Check data order
90 # MARC21
91 # 00*-00* are in numeric-alpha order in both directory and data (with no intervening in either directory or data)
92 }
93
94 function CheckField( $field ) {
95 parent::CheckField( $field );
96 if( substr( $field->tag, 0, 2 ) === "00" ) {
97 // No extra requirements on control fields
98 } else {
99 // Indicators must be length 2, numeric, lower case, or space
100 if( !preg_match("/^[0-9a-z ]{2}$/", $field->indicators) )
101 $this->exception("MARC21 8.3.5: indicators should be length 2, and each character should be lower case, numeric, or a space. " .
102 "So field '{$field->tag}' with indicators '{$field->indicators}' is not allowed." );
103 // Subfield identifiers must be length 1, numeric, or lower case
104 foreach( $field->subfields as $subfield ) {
105 if( !preg_match("/^[0-9a-z]$/", $subfield->identifier) )
106 $this->exception("MARC21 8.3.5: subfield identifiers should be length 1, and each character should be lower case or numeric. " .
107 "So field '{$field->tag}' with subfield '{$subfield->identifier}' is not allowed." );
108 }
109 }
110 }
111
112 function loadFromMnemonicString( $string ) {
113 $this->leader = array();
114 $this->fields = array();
115 $this->exceptions = array();
116 $this->raw = $string;
117 foreach( explode( "\r\n", $string ) as $line ) {
118 $field = $this->ParseMnemonicLine( $line );
119 if( $field !== false ) $this->AppendField( $field );
120 }
121 }
122
123 function loadFromXMLString( $string ) {
124 $this->leader = array(); # XXX ??
125 $this->fields = array();
126 $this->exceptions = array();
127 $this->raw = $string;
128
129 $doc = new DOMDocument();
130 $doc->loadXML($string);
131 $xpath = new DOMXpath($doc);
132
133 $leaderfield = $xpath->query('//record/leader');
134 if ($leaderfield) { # <leader>00838mas a2200301 a 4500</leader>
135 $leader = $leaderfield[0]->nodeValue;
136 if( strlen($leader) != 24 ) return false;
137 $this->leader = $leader;
138 }
139
140 # $field = new ISO2709Field( $tag, $data, $indicators, $subfields, $directoryEntry, $this );
141
142 $controlfield = $xpath->query('//record/controlfield');
143 if ($controlfield) { # <controlfield tag="001"> 92005291 </controlfield>
144 foreach ($controlfield as $cfd) {
145 $tag = $cfd->getAttribute('tag');
146 $data = $cfd->nodeValue;
147 $field = new ISO2709Field( $tag, $data );
148 $this->AppendField( $field );
149 }
150 }
151
152 $datafield = $xpath->query('//record/datafield');
153 if ($datafield) { # <datafield ind1=" " ind2=" " tag="035"><subfield code="9">AHE8121LM</subfield></datafield>
154 foreach ($datafield as $dfd) {
155 $tag = $dfd->getAttribute('tag');
156 $indicators = $dfd->getAttribute('ind1') . $dfd->getAttribute('ind2');
157 $subfields = array();
158 foreach ($dfd->childNodes as $subf) {
159 if ($subf->nodeType == '1') { # Element
160 $code = $subf->getAttribute('code');
161 $value = $subf->nodeValue;
162 $subfields[] = new ISO2709Subfield( $code, $value );
163 }
164 }
165 $field = new ISO2709Field( $tag, null, $indicators, $subfields);
166 $this->AppendField( $field );
167 }
168 }
169 }
170
171 function asXMLString( ) {
172 $str = '<record>';
173 $str .= '<leader>' . $this->leader . '</leader>';
174 foreach( $this->fields as $field ) {
175 $tag = escapeXmlAttribute($field->tag);
176 if ( isset($field->data ) ) {
177 $data = escapeXmlInnerText($field->data);
178 $str .= "<controlfield tag=\"$tag\">$data</controlfield>";
179 } else {
180 $ind1 = escapeXmlAttribute($field->indicators[0]);
181 $ind2 = escapeXmlAttribute($field->indicators[1]);
182 $str .= "<datafield tag=\"$tag\" ind1=\"$ind1\" ind2=\"$ind2\">";
183 foreach( $field->subfields as $subfield ) {
184 $code = escapeXmlAttribute($subfield->identifier);
185 $data = escapeXmlInnerText($subfield->data);
186 $str .= "<subfield code=\"$code\">$data</subfield>";
187 }
188 $str .= '</datafield>';
189 }
190 }
191 $str .= '</record>';
192 file_put_contents("/tmp/wiggle",$str);
193 return $str;
194 }
195
196 function loadFromString( $string ) {
197 if (preg_match("/^ *([^ ])/",$string,$m)) {
198 $starts_with = $m[1];
199 if ($starts_with == '<') {
200 return $this->loadFromXMLString( $string );
201 }
202 if ($starts_with == '=') {
203 return $this->loadFromMnemonicString( $string );
204 }
205 if (ctype_digit($starts_with)) {
206 return $this->loadFromBinaryString( $string );
207 }
208 return false;
209 }
210 return false;
211 }
212
213 function ParseMnemonicLine( $line ) {
214 $equ = substr( $line, 0, 1 );
215 $tag = substr( $line, 1, 3 );
216 $spa = substr( $line, 4, 2 );
217 $dat = substr( $line, 6 );
218 if( $equ !== "=" or $spa !== " " ) {
219 $this->exception("Mnemonic: unreadable line '$line'");
220 return false;
221 }
222 if( $tag == "LDR" or $tag == "000" ) {
223 $this->leader = $dat;
224 $this->indicatorLength = 2;
225 $this->identifierLength = 2;
226 $this->mnemonicType = ($tag == "LDR") ? "TRME" : "LCMB";
227 return false;
228 }
229 if( $this->mnemonicType === "TRME" ) {
230 $dat = str_replace( '$', ISO2709::SubfieldInitiator, $dat );
231 $dat = str_replace( '\\', ' ', $dat );
232 } else if( $this->mnemonicType === "LCMB" ) {
233 $dat = str_replace( '$', ISO2709::SubfieldInitiator, $dat );
234 $dat = str_replace( '#', ' ', $dat );
235 /* // These might not really be applicable
236 } else if( $this->mnemonicType === "OCLC" ) {
237 $dat = str_replace( 'ǂ', ISO2709::SubfieldInitiator, $dat );
238 // no space translation
239 } else if( $this->mnemonicType === "VGER" ) {
240 $dat = str_replace( '‡', ISO2709::SubfieldInitiator, $dat );
241 // no space translation
242 } else if( $this->mnemonicType === "ALMA" ) {
243 $dat = str_replace( '$$', ISO2709::SubfieldInitiator, $dat );
244 */
245 } else {
246 if( substr($tag,0,2) === "00" ) {
247 $dat = str_replace( '#', ' ', $dat );
248 $dat = str_replace( '\\', ' ', $dat );
249 } else {
250 if( $dat[0] == '\\' ) $dat[0] = ' ';
251 if( $dat[1] == '\\' ) $dat[1] = ' ';
252 if( $dat[0] == '#' ) $dat[0] = ' ';
253 if( $dat[1] == '#' ) $dat[1] = ' ';
254 if( strpos( $dat, ISO2709::SubfieldInitiator ) === false ) {
255 $dat = str_replace( '$', ISO2709::SubfieldInitiator, $dat );
256 }
257 }
258 }
259 return $this->ParseBinaryField( $tag, $dat, array() );
260 }
261
262 function AppendField( $field, $reorder = false ) {
263 if( is_string( $field ) ) {
264 $field = $this->ParseMnemonicLine( $field );
265 }
266 if( is_a( $field, "ISO2709Field" ) ) return parent::AppendField( $field, $reorder );
267 }
268
269
270 function AsMnemonicString( $leader_tag = 'LDR', $field_initiator = '=', $tag_terminator = ' ', $identifier_initiator = '$', $field_terminator = "\r\n", $record_terminator = "\r\n", $space_replacer = ' ') {
271 if( $this->mnemonicType === "TRME" ) return parent::AsMnemonicString( "LDR", "=", " ", "\$", "\r\n", "\r\n", "\\" );
272 if( $this->mnemonicType === "LCMB" ) return parent::AsMnemonicString( "000", "=", " ", "\$", "\r\n", "\r\n", "#" );
273 if( $this->mnemonicType === "VGER" ) return parent::AsMnemonicString( "LEADER", "=", " ", "|", "\r\n", "\r\n", "#" );
274 #return parent::AsMnemonicString( "LDR", "=", " ", "\$", "\r\n", "\r\n", " " );
275 return parent::AsMnemonicString( $leader_tag, $field_initiator, $tag_terminator, $identifier_initiator, $field_terminator, $record_terminator, $space_replacer );
276 }
277
278 function delFields( $pattern ) {
279 foreach( $this->fields as $k => $v ) {
280 $vmne = $v->AsMnemonicString( "=", " ", "\$", "\r\n", '\\' );
281 if( preg_match( "@^" . $pattern . "@", $vmne ) ) unset( $this->fields[$k] );
282 }
283 $this->fields = array_values( $this->fields );
284 }
285
286}
287
288function escapeXmlAttribute($str) {
289 return htmlspecialchars($str, ENT_XML1 | ENT_COMPAT, 'UTF-8');
290}
291
292function escapeXmlInnerText($str) {
293 return htmlspecialchars($str, ENT_XML1, 'UTF-8');
294}
escapeXmlAttribute($str)
Definition: MARC21.php:288
escapeXmlInnerText($str)
Definition: MARC21.php:292
set_default( $name, $value)
Definition: ISO2709.php:138
ParseBinaryField( $tag, $data, $directoryEntry)
Definition: ISO2709.php:384
exception( $string)
Definition: ISO2709.php:216
const RecordTerminator
Definition: ISO2709.php:121
const SubfieldInitiator
Definition: ISO2709.php:123
__construct( $input)
Definition: MARC21.php:20
__construct( $input)
Definition: MARC21.php:6
isTagValid( $tag)
Definition: MARC21.php:38
loadFromXMLString( $string)
Definition: MARC21.php:123
asXMLString()
Definition: MARC21.php:171
assertTagValid( $tag)
Definition: MARC21.php:42
$mnemonicType
Definition: MARC21.php:27
ParseMnemonicLine( $line)
Definition: MARC21.php:213
__construct()
Definition: MARC21.php:28
isTagRepeatable( $tag)
Definition: MARC21.php:50
AsMnemonicString( $leader_tag='LDR', $field_initiator='=', $tag_terminator=' ', $identifier_initiator='$', $field_terminator="\r\n", $record_terminator="\r\n", $space_replacer=' ')
Definition: MARC21.php:270
loadFromString( $string)
Definition: MARC21.php:196
CheckField( $field)
Definition: MARC21.php:94
tagOrder( $a, $b)
Definition: MARC21.php:55
AppendField( $field, $reorder=false)
Definition: MARC21.php:262
delFields( $pattern)
Definition: MARC21.php:278
assertTagOrder( $a, $b)
Definition: MARC21.php:72
loadFromMnemonicString( $string)
Definition: MARC21.php:112
loadFromBinaryString( $string, $fuzzy=true)
Definition: MARC21.php:80