fixed JSON geenration, added record label encoding, added lightweight record adapter, more tests

This commit is contained in:
Jörg Prante 2016-09-17 20:53:02 +02:00
parent e4623a9e5c
commit fa5f53fbd2
20 changed files with 348 additions and 193 deletions

View file

@ -0,0 +1,34 @@
/*
Copyright 2016 Jörg Prante
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.xbib.marc;
/**
*
*/
public class LightweightMarcRecordAdapter extends MarcRecordAdapter {
public LightweightMarcRecordAdapter(MarcRecordListener marcRecordListener) {
super(marcRecordListener);
this.builder = Marc.builder().lightweightRecord();
}
@Override
public void endRecord() {
super.endRecord();
builder = Marc.builder().lightweightRecord();
}
}

View file

@ -17,9 +17,7 @@
package org.xbib.marc;
import static org.xbib.marc.MarcXchangeConstants.BIBLIOGRAPHIC_TYPE;
import static org.xbib.marc.MarcXchangeConstants.FORMAT_ATTRIBUTE;
import static org.xbib.marc.MarcXchangeConstants.MARCXCHANGE_FORMAT;
import static org.xbib.marc.MarcXchangeConstants.TYPE_ATTRIBUTE;
import org.w3c.dom.Document;
import org.xbib.marc.dialects.aleph.AlephSequentialInputStream;
@ -495,12 +493,6 @@ public final class Marc {
}
if (builder.getProperties() != null) {
for (Map.Entry<String, Object> entry : builder.getProperties().entrySet()) {
if (FORMAT_ATTRIBUTE.equals(entry.getKey())) {
continue;
}
if (TYPE_ATTRIBUTE.equals(entry.getKey())) {
continue;
}
sax.getXMLReader().setProperty(entry.getKey(), entry.getValue());
}
}
@ -969,17 +961,12 @@ public final class Marc {
return this;
}
/**
* Not used as there is no known input with collection events yet.
*/
@Override
public void beginCollection() {
if (listener != null) {
listener.beginCollection();
}
if (defaultContentHandler != null) {
defaultContentHandler.beginCollection();
}
if (marcRecordListener != null) {
marcRecordListener.beginCollection();
}
// not used
}
@Override
@ -1042,17 +1029,12 @@ public final class Marc {
}
}
/**
* Not used as there is no known input with collection events yet.
*/
@Override
public void endCollection() {
if (listener != null) {
listener.endCollection();
}
if (defaultContentHandler != null) {
defaultContentHandler.endCollection();
}
if (marcRecordListener != null) {
marcRecordListener.endCollection();
}
// not used
}
public Marc.Builder recordLabel(RecordLabel recordLabel) {

View file

@ -563,6 +563,7 @@ public class MarcField implements Comparable<MarcField> {
* @param string the string to insert
* @return true if collection changed
*/
@Override
public boolean add(String string) {
ListIterator<String> it = listIterator();
boolean added = false;

View file

@ -16,7 +16,6 @@
*/
package org.xbib.marc;
import static org.xbib.marc.io.InformationSeparator.FS;
import static org.xbib.marc.io.InformationSeparator.GS;
import static org.xbib.marc.io.InformationSeparator.RS;
import static org.xbib.marc.io.InformationSeparator.US;
@ -141,11 +140,6 @@ public class MarcGenerator implements ChunkListener<byte[], BytesReference> {
return;
}
switch (separator) {
case FS: {
emitMarcField();
emitMarcRecord();
break;
}
case GS: {
emitMarcField();
emitMarcRecord();
@ -250,10 +244,6 @@ public class MarcGenerator implements ChunkListener<byte[], BytesReference> {
}
private void newRecord() throws IOException {
// checkguard
if (this.data == null || this.data.isEmpty()) {
return;
}
// skip line-feed (OCLC PICA quirk)
if (this.data.charAt(0) == '\n') {
this.data = data.substring(1);

View file

@ -22,7 +22,7 @@ import static org.xbib.marc.json.MarcJsonWriter.TYPE_TAG;
import org.xbib.marc.label.RecordLabel;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -33,7 +33,7 @@ import java.util.stream.Collectors;
/**
* A MARC record. This is an extended MARC record augmented with MarcXchange information.
*/
public class MarcRecord extends HashMap<String, Object> {
public class MarcRecord extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 5305809148724342653L;
@ -138,19 +138,15 @@ public class MarcRecord extends HashMap<String, Object> {
put(LEADER_TAG, recordLabel.toString());
for (MarcField marcField : marcFields) {
String tag = marcField.getTag();
if (marcField.isControl()) {
put(tag, marcField.getValue());
continue;
}
if (!containsKey(tag)) {
put(tag, new HashMap<>());
put(tag, new LinkedHashMap<>());
}
String indicator = marcField.getIndicator();
if (indicator != null) {
if (indicator != null && !indicator.isEmpty()) {
indicator = indicator.replace(' ', '_');
Map<String, Object> indicators = (Map<String, Object>) get(tag);
if (!indicators.containsKey(indicator)) {
indicators.put(indicator, new HashMap<>());
indicators.put(indicator, new LinkedHashMap<>());
}
Map<String, Object> subfields = (Map<String, Object>) indicators.get(indicator);
for (MarcField.Subfield subfield : marcField.getSubfields()) {
@ -167,6 +163,8 @@ public class MarcRecord extends HashMap<String, Object> {
subfields.put(subfield.getId(), subfield.getValue());
}
}
} else {
put(tag, marcField.getValue());
}
}
}

View file

@ -1,3 +1,19 @@
/*
Copyright 2016 Jörg Prante
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.xbib.marc;
import org.xbib.marc.label.RecordLabel;
@ -7,13 +23,13 @@ import org.xbib.marc.label.RecordLabel;
*/
public class MarcRecordAdapter implements MarcListener {
private final MarcRecordListener marcRecordListener;
protected final MarcRecordListener marcRecordListener;
private Marc.Builder builder;
protected Marc.Builder builder;
public MarcRecordAdapter(MarcRecordListener marcRecordListener) {
this.marcRecordListener = marcRecordListener;
this.builder = Marc.builder().lightweightRecord();
this.builder = Marc.builder();
}
@Override
@ -40,7 +56,7 @@ public class MarcRecordAdapter implements MarcListener {
@Override
public void endRecord() {
marcRecordListener.record(builder.buildRecord());
builder = Marc.builder().lightweightRecord();
builder = Marc.builder();
}
@Override

View file

@ -20,7 +20,7 @@ import org.xbib.marc.io.BytesStreamOutput;
import org.xbib.marc.io.DefaultChunk;
import org.xbib.marc.io.InformationSeparator;
import org.xbib.marc.io.SeparatorOutputStream;
import org.xbib.marc.transformer.value.MarcValueTransformer;
import org.xbib.marc.transformer.value.MarcValueTransformers;
import org.xbib.marc.xml.MarcContentHandler;
import java.io.Closeable;
@ -47,7 +47,7 @@ public class MarcWriter extends MarcContentHandler implements Flushable, Closeab
private final Charset charset;
private MarcValueTransformer marcValueTransformer;
private MarcValueTransformers marcValueTransformers;
private boolean fatalErrors;
@ -76,20 +76,8 @@ public class MarcWriter extends MarcContentHandler implements Flushable, Closeab
this.bytesStreamOutput = new BytesStreamOutput();
}
@Override
public MarcWriter setFormat(String format) {
super.setFormat(format);
return this;
}
@Override
public MarcWriter setType(String type) {
super.setType(type);
return this;
}
public MarcWriter setMarcValueTransformer(MarcValueTransformer marcValueTransformer) {
this.marcValueTransformer = marcValueTransformer;
public MarcWriter setMarcValueTransformers(MarcValueTransformers marcValueTransformers) {
this.marcValueTransformers = marcValueTransformers;
return this;
}
@ -138,30 +126,31 @@ public class MarcWriter extends MarcContentHandler implements Flushable, Closeab
return;
}
try {
MarcField marcField = marcValueTransformers != null ? marcValueTransformers.transformValue(field) : field;
bytesStreamOutput.reset();
// we clean up a bit. Write control field, and fields that are not empty.
// Do not care about the control field / data field order.
if (field.isControl()) {
String value = field.getValue();
if (marcField.isControl()) {
String value = marcField.getValue();
if (value != null && !value.isEmpty()) {
bytesStreamOutput.write(field.getTag().getBytes(StandardCharsets.ISO_8859_1));
bytesStreamOutput.write(transform(value).getBytes(charset));
bytesStreamOutput.write(marcField.getTag().getBytes(StandardCharsets.ISO_8859_1));
bytesStreamOutput.write(value.getBytes(charset));
out.chunk(new DefaultChunk(InformationSeparator.RS, bytesStreamOutput.bytes()));
}
} else if (!field.isEmpty()) {
bytesStreamOutput.write(field.getTag().getBytes(StandardCharsets.ISO_8859_1));
bytesStreamOutput.write(field.getIndicator().getBytes(StandardCharsets.ISO_8859_1));
String value = field.getValue();
} else if (!marcField.isEmpty()) {
bytesStreamOutput.write(marcField.getTag().getBytes(StandardCharsets.ISO_8859_1));
bytesStreamOutput.write(marcField.getIndicator().getBytes(StandardCharsets.ISO_8859_1));
String value = marcField.getValue();
if (value != null && !value.isEmpty()) {
bytesStreamOutput.write(transform(value).getBytes(charset));
bytesStreamOutput.write(value.getBytes(charset));
}
out.chunk(new DefaultChunk(InformationSeparator.RS, bytesStreamOutput.bytes()));
for (MarcField.Subfield subfield : field.getSubfields()) {
for (MarcField.Subfield subfield : marcField.getSubfields()) {
value = subfield.getValue();
if (value != null && !value.isEmpty()) {
bytesStreamOutput.reset();
bytesStreamOutput.write(subfield.getId().getBytes(StandardCharsets.ISO_8859_1));
bytesStreamOutput.write(transform(value).getBytes(charset));
bytesStreamOutput.write(value.getBytes(charset));
out.chunk(new DefaultChunk(InformationSeparator.US, bytesStreamOutput.bytes()));
}
}
@ -205,10 +194,6 @@ public class MarcWriter extends MarcContentHandler implements Flushable, Closeab
return exception;
}
private String transform(String value) {
return marcValueTransformer != null ? marcValueTransformer.transform(value) : value;
}
private void handleException(IOException e) {
exception = e;
if (fatalErrors) {

View file

@ -16,9 +16,11 @@
*/
package org.xbib.marc.json;
import org.xbib.marc.Marc;
import org.xbib.marc.MarcField;
import org.xbib.marc.MarcListener;
import org.xbib.marc.MarcRecord;
import org.xbib.marc.label.RecordLabel;
import org.xbib.marc.xml.MarcContentHandler;
import java.io.BufferedWriter;
@ -30,6 +32,9 @@ import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -44,22 +49,18 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
public static final String TYPE_TAG = "_TYPE";
private static final String EMPTY_STRING = "";
private static final String JSON_1 = "\":\"";
private final Lock lock = new ReentrantLock();
private final BufferedWriter writer;
private final StringBuilder sb;
private Marc.Builder builder;
private boolean fatalErrors = false;
private boolean jsonlines;
private int fieldCount;
private Exception exception;
public MarcJsonWriter(OutputStream out) throws IOException {
@ -77,8 +78,8 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
public MarcJsonWriter(Writer writer, boolean jsonlines) throws IOException {
this.writer = new BufferedWriter(writer);
this.sb = new StringBuilder();
this.fieldCount = 0;
this.jsonlines = jsonlines;
this.builder = Marc.builder();
}
public MarcJsonWriter setFatalErrors(boolean fatalErrors) {
@ -95,12 +96,14 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
@Override
public MarcJsonWriter setFormat(String format) {
super.setFormat(format);
builder.setFormat(format);
return this;
}
@Override
public MarcJsonWriter setType(String type) {
super.setType(type);
builder.setType(type);
return this;
}
@ -120,35 +123,20 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
@Override
public void beginRecord(String format, String type) {
super.beginRecord(format, type);
if (recordCounter.get() > 0) {
sb.append(jsonlines ? "\n" : ",");
}
sb.append("{");
String s = format != null ? format : this.format;
sb.append("\"").append(FORMAT_TAG).append("\":\"").append(escape(s)).append("\"");
fieldCount++;
s = type != null ? type : this.type;
if (fieldCount > 0) {
sb.append(",");
}
sb.append("\"").append(TYPE_TAG).append("\":\"").append(escape(s)).append("\"");
fieldCount++;
setFormat(format);
setType(type);
}
@Override
public void leader(String label) {
super.leader(label);
if (fieldCount > 0) {
sb.append(",");
}
sb.append("\"").append(LEADER_TAG).append("\":\"").append(label).append("\"");
fieldCount++;
builder.recordLabel(RecordLabel.builder().from(label.toCharArray()).build());
}
@Override
public void field(MarcField field) {
super.field(field);
fieldCount = toJson(field, fieldCount, sb);
builder.addField(field);
}
@Override
@ -174,14 +162,14 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
@Override
public void endRecord() {
super.endRecord();
try {
sb.append("}");
writer.write(sb.toString());
sb.setLength(0);
recordCounter.incrementAndGet();
} catch (IOException e) {
handleException(e);
if (format != null) {
builder.setFormat(format);
}
if (type != null) {
builder.setType(type);
}
record(builder.buildRecord());
builder = Marc.builder();
}
@Override
@ -211,30 +199,107 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
* Format MARC record as key-oriented JSON.
* @param sb a string builder to append JSON to
*/
@SuppressWarnings("unchecked")
private void toJson(MarcRecord marcRecord, StringBuilder sb) {
if (marcRecord.isEmpty()) {
return;
}
if (recordCounter.get() > 0) {
sb.append(jsonlines ? "\n" : ",");
}
sb.append("{");
int recordFieldCount = 0;
if (format != null) {
sb.append("\"_FORMAT\":\"").append(escape(format)).append("\"");
recordFieldCount++;
}
if (type != null) {
if (recordFieldCount > 0) {
int c0 = 0;
for (Map.Entry<String, Object> tags : marcRecord.entrySet()) {
if (c0 > 0) {
sb.append(",");
}
sb.append("\"_TYPE\":\"").append(escape(type)).append("\"");
recordFieldCount++;
}
if (recordFieldCount > 0) {
sb.append(",");
}
sb.append("\"_LEADER\":\"").append(marcRecord.getRecordLabel()).append("\"");
recordFieldCount++;
for (MarcField field : marcRecord.getFields()) {
recordFieldCount = toJson(field, recordFieldCount, sb);
String tag = tags.getKey();
sb.append("\"").append(tag).append("\":");
Object o = tags.getValue();
if (!(o instanceof List)) {
o = Collections.singletonList(o);
}
List<?> list = (List<?>) o;
if (list.size() > 1) {
sb.append("[");
}
int c1 = 0;
for (Object value : list) {
if (c1 > 0) {
sb.append(",");
}
if (value instanceof Map) {
sb.append("{");
int c2 = 0;
for (Map.Entry<String, Object> indicators : ((Map<String, Object>) value).entrySet()) {
if (c2 > 0) {
sb.append(",");
}
String indicator = indicators.getKey();
sb.append("\"").append(indicator).append("\":");
o = indicators.getValue();
if (!(o instanceof List)) {
o = Collections.singletonList(o);
}
List<?> list2 = (List<?>) o;
if (list2.size() > 1) {
sb.append("[");
}
int c3 = 0;
for (Object value2 : list2) {
if (c3 > 0) {
sb.append(",");
}
if (value2 instanceof Map) {
sb.append("{");
Map<String, Object> map = (Map<String, Object>) value2;
int c4 = 0;
for (Map.Entry<String, Object> subfield : map.entrySet()) {
if (c4 > 0) {
sb.append(",");
}
sb.append("\"").append(subfield.getKey()).append("\":");
if (subfield.getValue() instanceof List) {
sb.append("[");
int c5 = 0;
for (String s : (List<String>)subfield.getValue()) {
if (c5 > 0) {
sb.append(",");
}
sb.append("\"").append(escape(s)).append("\"");
c5++;
}
sb.append("]");
} else {
sb.append("\"").append(escape(subfield.getValue().toString())).append("\"");
}
c4++;
}
sb.append("}");
} else {
sb.append("\"").append(escape(value2.toString())).append("\"");
}
c3++;
}
if (list2.size() > 1) {
sb.append("]");
}
c2++;
}
sb.append("}");
} else {
if (value == null) {
sb.append("null");
} else {
sb.append("\"").append(escape(value.toString())).append("\"");
}
}
c1++;
}
if (list.size() > 1) {
sb.append("]");
}
c0++;
}
sb.append('}');
if (jsonlines) {
@ -242,57 +307,6 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
}
}
/**
* Print a key-oriented JSON represenation of this MARC field.
*
* @param fieldCount how many MARC field are writte before. Used for emitting a comma
* if necessary.
* @param sb the string builder to attach the JSON representation to.
*
* @return the new MARC field count. Empty MARC fields not not increase the field count.
*/
private int toJson(MarcField marcField, int fieldCount, StringBuilder sb) {
int count = fieldCount;
if (marcField.isControl()) {
if (count > 0) {
sb.append(",");
}
sb.append("\"").append(marcField.getTag()).append(JSON_1).append(escape(marcField.getValue())).append("\"");
count++;
return count;
} else if (!marcField.isEmpty()) {
if (count > 0) {
sb.append(",");
}
String tag = marcField.getTag();
String indicator = marcField.getIndicator();
if (indicator == null) {
indicator = EMPTY_STRING;
}
sb.append("\"").append(tag).append("\":{\"")
.append(indicator.replace(' ', '_')).append("\":");
if (marcField.getSubfields().size() == 1) {
MarcField.Subfield subfield = marcField.getSubfields().get(0);
sb.append("{\"").append(subfield.getId()).append(JSON_1).append(escape(subfield.getValue())).append("\"}");
} else {
sb.append("[");
StringBuilder subfieldBuilder = new StringBuilder();
for (MarcField.Subfield subfield : marcField.getSubfields()) {
if (subfieldBuilder.length() > 0) {
subfieldBuilder.append(",");
}
subfieldBuilder.append("{\"").append(subfield.getId()).append(JSON_1)
.append(escape(subfield.getValue())).append("\"}");
}
sb.append(subfieldBuilder);
sb.append("]");
}
sb.append("}");
count++;
}
return count;
}
public Exception getException() {
return exception;
}

View file

@ -76,6 +76,10 @@ public class RecordLabel {
return builder.typeOfControl;
}
public Encoding getEncoding() {
return builder.encoding;
}
public int getIndicatorLength() {
return builder.indicatorLength;
}
@ -151,6 +155,8 @@ public class RecordLabel {
private TypeOfControl typeOfControl;
private Encoding encoding;
private Builder() {
cfix = empty;
repair();
@ -313,6 +319,17 @@ public class RecordLabel {
return this;
}
/**
* Set encoding. See {@link Encoding}.
* @param encoding the encoding
* @return this builder
*/
public Builder setEncoding(Encoding encoding) {
this.encoding = encoding;
cfix[9] = encoding.getChar();
return this;
}
/**
* Indicator length is a numeric digit giving the length of the indicators.
*

View file

@ -76,7 +76,6 @@ public class ConcurrencyTest {
assertNull(writer.getException());
assertEquals(n * 292, writer.getRecordCounter());
}
//assertEquals(8175000, file.length());
}
/**
@ -116,7 +115,6 @@ public class ConcurrencyTest {
assertNull(writer.getException());
assertEquals(n * 292, writer.getRecordCounter());
}
assertEquals(175761 * n + 1, file.length());
}
/**
* Write JSON lines format. This is shorter than array, because commas are not required.
@ -155,6 +153,5 @@ public class ConcurrencyTest {
assertNull(writer.getException());
assertEquals(n * 292, writer.getRecordCounter());
}
assertEquals(176053 * n - 1, file.length());
}
}

View file

@ -198,4 +198,28 @@ public class MarcRecordTest extends Assert {
assertThat(file, CompareMatcher.isIdenticalTo(getClass().getResource(s + ".xml").openStream()));
}
@Test
public void testIRMARC8AsLightweightRecordAdapter() throws Exception {
String s = "IRMARC8.bin";
InputStream in = getClass().getResource(s).openStream();
File file = File.createTempFile(s + ".", ".xml");
file.deleteOnExit();
FileOutputStream out = new FileOutputStream(file);
MarcValueTransformers marcValueTransformers = new MarcValueTransformers();
marcValueTransformers.setMarcValueTransformer(value -> Normalizer.normalize(value, Normalizer.Form.NFC));
try (MarcXchangeWriter writer = new MarcXchangeWriter(out)
.setMarcValueTransformers(marcValueTransformers)) {
writer.startDocument(); // just write XML processing instruction
Marc.builder()
.setInputStream(in)
.setCharset(Charset.forName("ANSEL"))
.setMarcListener(new LightweightMarcRecordAdapter(writer))
.build()
.writeCollection();
assertNull(writer.getException());
writer.endDocument();
}
assertThat(file, CompareMatcher.isIdenticalTo(getClass().getResource(s + ".xml").openStream()));
}
}

View file

@ -18,6 +18,7 @@ package org.xbib.marc;
import org.junit.Assert;
import org.junit.Test;
import org.xbib.marc.transformer.value.MarcValueTransformers;
import org.xbib.marc.xml.MarcXchangeWriter;
import org.xmlunit.matchers.CompareMatcher;
@ -68,4 +69,46 @@ public class MarcWriterTest extends Assert {
assertThat(xmlFile, CompareMatcher.isIdenticalTo(getClass().getResource(s + ".xml").openStream()));
}
}
@Test
public void testUtf8MarcWriterWithTransformer() throws Exception {
for (String s : new String[]{
"summerland.mrc",
"chabon.mrc",
"chabon-loc.mrc"
}) {
InputStream in = getClass().getResource(s).openStream();
File file = File.createTempFile(s, ".utf8");
file.deleteOnExit();
FileOutputStream out = new FileOutputStream(file);
MarcValueTransformers marcValueTransformers = new MarcValueTransformers();
marcValueTransformers.setMarcValueTransformer("245$10$a", t -> t.replaceAll("Chabon","Chibon"));
try (MarcWriter writer = new MarcWriter(out, StandardCharsets.UTF_8)
.setMarcValueTransformers(marcValueTransformers)) {
Marc.builder()
.setInputStream(in)
.setCharset(Charset.forName("ANSEL"))
.setMarcListener(writer)
.build()
.writeCollection();
assertNull(writer.getException());
}
// re-read files with our Marc builder and write as MarcXchange
File xmlFile = File.createTempFile(s, ".utf8");
xmlFile.deleteOnExit();
out = new FileOutputStream(xmlFile);
marcValueTransformers = new MarcValueTransformers();
marcValueTransformers.setMarcValueTransformer("245$10$a", t -> t.replaceAll("Chibon","Chabon"));
try (MarcXchangeWriter writer = new MarcXchangeWriter(out)
.setMarcValueTransformers(marcValueTransformers)) {
Marc.builder()
.setInputStream(new FileInputStream(file))
.setMarcListener(writer)
.build()
.writeCollection();
}
// compare result
assertThat(xmlFile, CompareMatcher.isIdenticalTo(getClass().getResource(s + ".xml").openStream()));
}
}
}

View file

@ -22,6 +22,7 @@ import org.junit.Test;
import org.xbib.marc.Marc;
import org.xbib.marc.MarcRecordAdapter;
import org.xbib.marc.MarcXchangeConstants;
import org.xbib.marc.xml.MarcContentHandler;
import java.io.File;
import java.io.FileInputStream;
@ -83,10 +84,10 @@ public class MarcJsonWriterTest {
file.deleteOnExit();
FileOutputStream out = new FileOutputStream(file);
try (MarcJsonWriter writer = new MarcJsonWriter(out)
.setFormat(MarcXchangeConstants.MARCXCHANGE_FORMAT)
.setType(MarcXchangeConstants.BIBLIOGRAPHIC_TYPE)
) {
Marc.builder()
.setFormat(MarcXchangeConstants.MARCXCHANGE_FORMAT)
.setType(MarcXchangeConstants.BIBLIOGRAPHIC_TYPE)
.setInputStream(in)
.setCharset(Charset.forName("ANSEL"))
.setMarcRecordListener(writer)
@ -115,11 +116,10 @@ public class MarcJsonWriterTest {
File file = File.createTempFile(s + ".", ".json");
file.deleteOnExit();
FileOutputStream out = new FileOutputStream(file);
try (MarcJsonWriter writer = new MarcJsonWriter(out)
.setFormat(MarcXchangeConstants.MARCXCHANGE_FORMAT)
.setType(MarcXchangeConstants.BIBLIOGRAPHIC_TYPE)
) {
try (MarcJsonWriter writer = new MarcJsonWriter(out)) {
Marc.builder()
.setFormat(MarcXchangeConstants.MARCXCHANGE_FORMAT)
.setType(MarcXchangeConstants.BIBLIOGRAPHIC_TYPE)
.setInputStream(in)
.setCharset(Charset.forName("ANSEL"))
.setMarcListener(new MarcRecordAdapter(writer))
@ -130,4 +130,29 @@ public class MarcJsonWriterTest {
new FileInputStream(file));
}
}
@Test
public void testAlephPublishRecordAdapterJson() throws Exception {
String s = "HT016424175.xml";
InputStream in = getClass().getResource("/org/xbib/marc/dialects/mab/" + s).openStream();
File file = File.createTempFile(s + ".", ".json");
FileOutputStream out = new FileOutputStream(file);
try (MarcJsonWriter writer = new MarcJsonWriter(out, true)
.setFormat(MarcXchangeConstants.MARCXCHANGE_FORMAT)
.setType(MarcXchangeConstants.BIBLIOGRAPHIC_TYPE)
) {
MarcContentHandler contentHandler = new MarcContentHandler();
contentHandler.addNamespace("http://www.ddb.de/professionell/mabxml/mabxml-1.xsd");
contentHandler.setFormat("MARC21");
contentHandler.setType("Bibliographic");
contentHandler.setMarcListener(new MarcRecordAdapter(writer));
Marc.builder()
.setInputStream(in)
.setContentHandler(contentHandler)
.build()
.xmlReader().parse();
}
assertStream(s, getClass().getResource("/org/xbib/marc/json/" + s + ".json").openStream(),
new FileInputStream(file));
}
}

View file

@ -125,6 +125,12 @@ public class RecordLabelTest {
assertEquals(RecordStatus.DELETED, recordLabel.getRecordStatus());
}
@Test
public void testEncoding() {
RecordLabel recordLabel = RecordLabel.builder().setEncoding(Encoding.MARC8).build();
assertEquals(Encoding.MARC8, recordLabel.getEncoding());
}
@Test
public void testRecordLabelBuilder() {
RecordLabel recordLabel = RecordLabel.builder()

View file

@ -31,6 +31,7 @@ public class ToolTest {
public void testToolSimple() throws Exception {
String[] args = {
"--in", "src/test/resources/org/xbib/marc/chabon.mrc",
"--charset", "ANSEL",
"--out", "build/chabon.mrc.xml"
};
exit.expectSystemExitWithStatus(0);
@ -42,6 +43,7 @@ public class ToolTest {
String[] args = {
"--in", "src/test/resources/org/xbib/marc/summerland.mrc",
"--out", "build/summerland.mrc.xml",
"--charset", "ANSEL",
"--schema", "MARC21",
"--stylesheet", "http://www.loc.gov/standards/mods/v3/MARC21slim2MODS3.xsl",
"--result", "build/summerland.mods"

View file

@ -67,4 +67,24 @@ public class MarcEventConsumerTest extends Assert {
assertNull(writer.getException());
assertThat(sw.toString(), CompareMatcher.isIdenticalTo(getClass().getResource(s + "-eventconsumer.xml").openStream()));
}
@Test
public void testMarcXchangeWriterWithEventConsumer() throws Exception {
String s = "HT016424175.xml";
InputStream in = getClass().getResourceAsStream(s);
MarcXchangeEventConsumer consumer = new MarcXchangeEventConsumer();
consumer.addNamespace("http://www.ddb.de/professionell/mabxml/mabxml-1.xsd");
MarcXchangeWriter writer = new MarcXchangeWriter(consumer);
writer.setFormat("AlephXML").setType("Bibliographic");
writer.startDocument();
Marc.builder()
.setInputStream(in)
.setCharset(StandardCharsets.UTF_8)
.setFormat("AlephXML")
.setType("Bibliographic")
.build()
.writeCollection();
writer.endDocument();
assertNull(writer.getException());
}
}

View file

@ -0,0 +1 @@
{"_FORMAT":"MARC21","_TYPE":"Bibliographic","_LEADER":"00000 M2.01200024 000h","LDR":"------M2.01200024------h","FMT":"MH","001":{"_1":{"a":"HT016424175"}},"002":{"a1":{"a":"20100705"}},"026":{"_1":{"a":"HBZHT016424175"}},"030":"a|1uc||||||1|","036":{"a1":{"a":"DE"}},"037":{"b1":{"a":"lat"}},"050":"a|||||||||||||","051":"am||||||","070":{"_1":{"a":"575"}},"080":{"_1":{"a":"60"}},"100":{"_1":{"p":"Hildegardis","c":"Bingensis","d":"1098-1179","9":"(DE-588)118550993"}},"101":{"_1":{"p":["Hildegard","Hildegard","Hildegard","Hildegardis","Hildegardis","Hildegarde","Ildegarda","Hildegardis","Hildegard","Hildegardis","Hildegardt","Hildegardis","Bingen, Hildegard <<von>>","Hildegard","Hildegardis","Ildegarda","Ildegarda","Hildegardis","Hildegard","Hildegarde","Ildegarda","Hildegardis","Hildegarda","Hildegarda","Hildegard","Childegard","Bingen, Childegard <<ot>>","Bingen, Hildegarde <<de>>","Hildegard","Hildegardis von Bingen","Hildegard","Hildegardis"],"d":["1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179","1098-1179"],"c":["Heilige, 1098-1179","Sankt","Sancta","Abbatissa","Sainte","de Bingen","de Monte Sancti Ruperti","die Heilige","de Bingen","of Bingen","Sancta Abatissa","Santa","Sant'","Abbess","Heilige","de Bingen","di Bingen","Abatissa","di Bingen","de Bingen","Bingeniläinen","ot Bingen","von Bingen","Sainte","de Alemannia"]}},"104":{"b1":{"p":"Escot, Pozzi","d":"1933-","b":"[Bearb.]","9":"(DE-588)128917687"}},"105":{"_1":{"p":"Pozzi Escot, Olga","d":"1933-"}},"304":{"b1":{"a":"Unde quocumque"}},"331":{"_1":{"a":"Unde quocumque"}},"334":{"_1":{"a":"Musikdruck"}},"359":{"_1":{"a":"Hildegard von Bingen"}},"425":{"_1":{"a":"c 1994"},"a1":{"a":"1994"}},"503":{"a1":{"a":"Transkription der mittelalterlichen Neumen in moderne Notation"}},"516":{"_1":{"a":"Melodien mit unterlegtem Text"}},"590":{"_1":{"a":"<<The>> Ursula Antiphons ... [Musikdruck]"}},"591":{"_1":{"a":"Hildegard von Bingen"}},"594":{"_1":{"a":"[Kassel]"}},"595":{"_1":{"a":"1994"}},"599":{"_1":{"a":"HT016424145"}},"SYS":"018117852"}

View file

@ -1 +1 @@
[{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"01488cam 2200349 a 4500","001":"11939876","005":"20041229190604.0","008":"000313s2000 nyu 000 1 eng ","906":{"__":[{"a":"7"},{"b":"cbc"},{"c":"orignew"},{"d":"1"},{"e":"ocip"},{"f":"20"},{"g":"y-gencatlg"}]},"925":{"0_":[{"a":"acquire"},{"b":"2 shelf copies"},{"x":"policy default"}]},"955":{"__":[{"a":"to HLCD pc03 03-13-00; lh08 to subj. 03-14-00; lh06 03-22-00; lk02 03-22-00; to Dewey 03-22-00; aa05 03-23-00; ps13 2001-11-06 bk rec'd, to CIP ver."},{"f":"pv08 2001-11-07 CIP ver. to BCCD"}]},"010":{"__":{"a":" 00029063 "}},"020":{"__":{"a":"0679450041 (acid-free paper)"}},"040":{"__":[{"a":"DLC"},{"c":"DLC"},{"d":"DLC"}]},"043":{"__":{"a":"n-us-ny"}},"050":{"00":[{"a":"PS3553.H15"},{"b":"A82 2000"}]},"082":{"00":[{"a":"813/.54"},{"2":"21"}]},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"14":[{"a":"The amazing adventures of Kavalier and Clay :"},{"b":"a novel /"},{"c":"Michael Chabon."}]},"260":{"__":[{"a":"New York :"},{"b":"Random House,"},{"c":"c2000."}]},"300":{"__":[{"a":"639 p. ;"},{"c":"25 cm."}]},"650":{"_0":[{"a":"Comic books, strips, etc."},{"x":"Authorship"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Heroes in mass media"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Czech Americans"},{"v":"Fiction."}]},"651":{"_0":[{"a":"New York (N.Y.)"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Young men"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Cartoonists"},{"v":"Fiction."}]},"655":{"_7":[{"a":"Humorous stories."},{"2":"gsafd"}]},"655":{"_7":[{"a":"Bildungsromane."},{"2":"gsafd"}]},"856":{"42":[{"3":"Contributor biographical information"},{"u":"http://www.loc.gov/catdir/bios/random052/00029063.html"}]},"856":{"41":[{"3":"Sample text"},{"u":"http://www.loc.gov/catdir/samples/random044/00029063.html"}]},"856":{"42":[{"3":"Publisher description"},{"u":"http://www.loc.gov/catdir/description/random0411/00029063.html"}]}},{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"01185cam 2200301 a 4500","001":"12883376","005":"20030616111422.0","008":"020805s2002 nyu j 000 1 eng ","906":{"__":[{"a":"7"},{"b":"cbc"},{"c":"orignew"},{"d":"1"},{"e":"ocip"},{"f":"20"},{"g":"y-gencatlg"}]},"925":{"0_":[{"a":"acquire"},{"b":"2 shelf copies"},{"x":"policy default"}]},"955":{"__":[{"a":"pc14 2002-08-05 to HLCD"},{"c":"lh08 2002-08-06 to subj.;"},{"d":"lb11 2002-09-05"},{"e":"lb05 2002-09-06 to cip"},{"a":"ps09 2003-03-04 1 copy rec'd., to CIP ver."},{"f":"pv01 2003-03-17 CIP ver to BCCD"},{"a":"ld11 2003-05-12 cp2 to BCCD"}]},"010":{"__":{"a":" 2002027497"}},"020":{"__":{"a":"0786808772"}},"020":{"__":{"a":"0786816155 (pbk.)"}},"040":{"__":[{"a":"DLC"},{"c":"DLC"},{"d":"DLC"}]},"042":{"__":{"a":"lcac"}},"050":{"00":[{"a":"PZ7.C3315"},{"b":"Su 2002"}]},"082":{"00":[{"a":"[Fic]"},{"2":"21"}]},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"10":[{"a":"Summerland /"},{"c":"Michael Chabon."}]},"250":{"__":{"a":"1st ed."}},"260":{"__":[{"a":"New York :"},{"b":"Miramax Books/Hyperion Books for Children,"},{"c":"c2002."}]},"300":{"__":[{"a":"500 p. ;"},{"c":"22 cm."}]},"520":{"__":{"a":"Ethan Feld, the worst baseball player in the history of the game, finds himself recruited by a 100-year-old scout to help a band of fairies triumph over an ancient enemy."}},"650":{"_1":{"a":"Fantasy."}},"650":{"_1":[{"a":"Baseball"},{"v":"Fiction."}]},"650":{"_1":[{"a":"Magic"},{"v":"Fiction."}]},"952":{"__":{"a":"II lb11 09-05-02"}}}]
[{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"01488cam 2200349 a 4500","001":"11939876","005":"20041229190604.0","008":"000313s2000 nyu 000 1 eng ","906":{"__":{"a":"7","b":"cbc","c":"orignew","d":"1","e":"ocip","f":"20","g":"y-gencatlg"}},"925":{"0_":{"a":"acquire","b":"2 shelf copies","x":"policy default"}},"955":{"__":{"a":"to HLCD pc03 03-13-00; lh08 to subj. 03-14-00; lh06 03-22-00; lk02 03-22-00; to Dewey 03-22-00; aa05 03-23-00; ps13 2001-11-06 bk rec'd, to CIP ver.","f":"pv08 2001-11-07 CIP ver. to BCCD"}},"010":{"__":{"a":" 00029063 "}},"020":{"__":{"a":"0679450041 (acid-free paper)"}},"040":{"__":{"a":"DLC","c":"DLC","d":"DLC"}},"043":{"__":{"a":"n-us-ny"}},"050":{"00":{"a":"PS3553.H15","b":"A82 2000"}},"082":{"00":{"a":"813/.54","2":"21"}},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"14":{"a":"The amazing adventures of Kavalier and Clay :","b":"a novel /","c":"Michael Chabon."}},"260":{"__":{"a":"New York :","b":"Random House,","c":"c2000."}},"300":{"__":{"a":"639 p. ;","c":"25 cm."}},"650":{"_0":{"a":["Comic books, strips, etc.","Heroes in mass media","Czech Americans","Young men","Cartoonists"],"x":"Authorship","v":["Fiction.","Fiction.","Fiction.","Fiction.","Fiction."]}},"651":{"_0":{"a":"New York (N.Y.)","v":"Fiction."}},"655":{"_7":{"a":["Humorous stories.","Bildungsromane."],"2":["gsafd","gsafd"]}},"856":{"42":{"3":["Contributor biographical information","Publisher description"],"u":["http://www.loc.gov/catdir/bios/random052/00029063.html","http://www.loc.gov/catdir/description/random0411/00029063.html"]},"41":{"3":"Sample text","u":"http://www.loc.gov/catdir/samples/random044/00029063.html"}}},{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"01185cam 2200301 a 4500","001":"12883376","005":"20030616111422.0","008":"020805s2002 nyu j 000 1 eng ","906":{"__":{"a":"7","b":"cbc","c":"orignew","d":"1","e":"ocip","f":"20","g":"y-gencatlg"}},"925":{"0_":{"a":"acquire","b":"2 shelf copies","x":"policy default"}},"955":{"__":{"a":["pc14 2002-08-05 to HLCD","ps09 2003-03-04 1 copy rec'd., to CIP ver.","ld11 2003-05-12 cp2 to BCCD"],"c":"lh08 2002-08-06 to subj.;","d":"lb11 2002-09-05","e":"lb05 2002-09-06 to cip","f":"pv01 2003-03-17 CIP ver to BCCD"}},"010":{"__":{"a":" 2002027497"}},"020":{"__":{"a":["0786808772","0786816155 (pbk.)"]}},"040":{"__":{"a":"DLC","c":"DLC","d":"DLC"}},"042":{"__":{"a":"lcac"}},"050":{"00":{"a":"PZ7.C3315","b":"Su 2002"}},"082":{"00":{"a":"[Fic]","2":"21"}},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"10":{"a":"Summerland /","c":"Michael Chabon."}},"250":{"__":{"a":"1st ed."}},"260":{"__":{"a":"New York :","b":"Miramax Books/Hyperion Books for Children,","c":"c2002."}},"300":{"__":{"a":"500 p. ;","c":"22 cm."}},"520":{"__":{"a":"Ethan Feld, the worst baseball player in the history of the game, finds himself recruited by a 100-year-old scout to help a band of fairies triumph over an ancient enemy."}},"650":{"_1":{"a":["Fantasy.","Baseball","Magic"],"v":["Fiction.","Fiction."]}},"952":{"__":{"a":"II lb11 09-05-02"}}}]

View file

@ -1 +1 @@
[{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"00759cam a2200229 a 4500","001":"11939876","005":"20041229190604.0","008":"000313s2000 nyu 000 1 eng ","020":{"__":{"a":"0679450041 (acid-free paper)"}},"040":{"__":[{"a":"DLC"},{"c":"DLC"},{"d":"DLC"}]},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"14":[{"a":"The amazing adventures of Kavalier and Clay :"},{"b":"a novel /"},{"c":"Michael Chabon."}]},"260":{"__":[{"a":"New York :"},{"b":"Random House,"},{"c":"c2000."}]},"300":{"__":[{"a":"639 p. ;"},{"c":"25 cm."}]},"650":{"_0":[{"a":"Comic books, strips, etc."},{"x":"Authorship"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Heroes in mass media"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Czech Americans"},{"v":"Fiction."}]},"651":{"_0":[{"a":"New York (N.Y.)"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Young men"},{"v":"Fiction."}]},"650":{"_0":[{"a":"Cartoonists"},{"v":"Fiction."}]},"655":{"_7":[{"a":"Humorous stories."},{"2":"gsafd"}]},"655":{"_7":[{"a":"Bildungsromane."},{"2":"gsafd"}]}},{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"00714cam a2200205 a 4500","001":"12883376","005":"20030616111422.0","008":"020805s2002 nyu j 000 1 eng ","020":{"__":{"a":"0786808772"}},"020":{"__":{"a":"0786816155 (pbk.)"}},"040":{"__":[{"a":"DLC"},{"c":"DLC"},{"d":"DLC"}]},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"10":[{"a":"Summerland /"},{"c":"Michael Chabon."}]},"250":{"__":{"a":"1st ed."}},"260":{"__":[{"a":"New York :"},{"b":"Miramax Books/Hyperion Books for Children,"},{"c":"c2002."}]},"300":{"__":[{"a":"500 p. ;"},{"c":"22 cm."}]},"520":{"__":{"a":"Ethan Feld, the worst baseball player in the history of the game, finds himself recruited by a 100-year-old scout to help a band of fairies triumph over an ancient enemy."}},"650":{"_1":{"a":"Fantasy."}},"650":{"_1":[{"a":"Baseball"},{"v":"Fiction."}]},"650":{"_1":[{"a":"Magic"},{"v":"Fiction."}]}}]
[{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"00759cam a2200229 a 4500","001":"11939876","005":"20041229190604.0","008":"000313s2000 nyu 000 1 eng ","020":{"__":{"a":"0679450041 (acid-free paper)"}},"040":{"__":{"a":"DLC","c":"DLC","d":"DLC"}},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"14":{"a":"The amazing adventures of Kavalier and Clay :","b":"a novel /","c":"Michael Chabon."}},"260":{"__":{"a":"New York :","b":"Random House,","c":"c2000."}},"300":{"__":{"a":"639 p. ;","c":"25 cm."}},"650":{"_0":{"a":["Comic books, strips, etc.","Heroes in mass media","Czech Americans","Young men","Cartoonists"],"x":"Authorship","v":["Fiction.","Fiction.","Fiction.","Fiction.","Fiction."]}},"651":{"_0":{"a":"New York (N.Y.)","v":"Fiction."}},"655":{"_7":{"a":["Humorous stories.","Bildungsromane."],"2":["gsafd","gsafd"]}}},{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"00714cam a2200205 a 4500","001":"12883376","005":"20030616111422.0","008":"020805s2002 nyu j 000 1 eng ","020":{"__":{"a":["0786808772","0786816155 (pbk.)"]}},"040":{"__":{"a":"DLC","c":"DLC","d":"DLC"}},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"10":{"a":"Summerland /","c":"Michael Chabon."}},"250":{"__":{"a":"1st ed."}},"260":{"__":{"a":"New York :","b":"Miramax Books/Hyperion Books for Children,","c":"c2002."}},"300":{"__":{"a":"500 p. ;","c":"22 cm."}},"520":{"__":{"a":"Ethan Feld, the worst baseball player in the history of the game, finds himself recruited by a 100-year-old scout to help a band of fairies triumph over an ancient enemy."}},"650":{"_1":{"a":["Fantasy.","Baseball","Magic"],"v":["Fiction.","Fiction."]}}}]

View file

@ -1 +1 @@
[{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"00714cam a2200205 a 4500","001":"12883376","005":"20030616111422.0","008":"020805s2002 nyu j 000 1 eng ","020":{"__":{"a":"0786808772"}},"020":{"__":{"a":"0786816155 (pbk.)"}},"040":{"__":[{"a":"DLC"},{"c":"DLC"},{"d":"DLC"}]},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"10":[{"a":"Summerland /"},{"c":"Michael Chabon."}]},"250":{"__":{"a":"1st ed."}},"260":{"__":[{"a":"New York :"},{"b":"Miramax Books/Hyperion Books for Children,"},{"c":"c2002."}]},"300":{"__":[{"a":"500 p. ;"},{"c":"22 cm."}]},"520":{"__":{"a":"Ethan Feld, the worst baseball player in the history of the game, finds himself recruited by a 100-year-old scout to help a band of fairies triumph over an ancient enemy."}},"650":{"_1":{"a":"Fantasy."}},"650":{"_1":[{"a":"Baseball"},{"v":"Fiction."}]},"650":{"_1":[{"a":"Magic"},{"v":"Fiction."}]}}]
[{"_FORMAT":"MarcXchange","_TYPE":"Bibliographic","_LEADER":"00714cam a2200205 a 4500","001":"12883376","005":"20030616111422.0","008":"020805s2002 nyu j 000 1 eng ","020":{"__":{"a":["0786808772","0786816155 (pbk.)"]}},"040":{"__":{"a":"DLC","c":"DLC","d":"DLC"}},"100":{"1_":{"a":"Chabon, Michael."}},"245":{"10":{"a":"Summerland /","c":"Michael Chabon."}},"250":{"__":{"a":"1st ed."}},"260":{"__":{"a":"New York :","b":"Miramax Books/Hyperion Books for Children,","c":"c2002."}},"300":{"__":{"a":"500 p. ;","c":"22 cm."}},"520":{"__":{"a":"Ethan Feld, the worst baseball player in the history of the game, finds himself recruited by a 100-year-old scout to help a band of fairies triumph over an ancient enemy."}},"650":{"_1":{"a":["Fantasy.","Baseball","Magic"],"v":["Fiction.","Fiction."]}}}]