Index: tika-parsers/src/main/resources/META-INF/services/org.apache.tika.parser.Parser
===================================================================
--- tika-parsers/src/main/resources/META-INF/services/org.apache.tika.parser.Parser	(revision 933894)
+++ tika-parsers/src/main/resources/META-INF/services/org.apache.tika.parser.Parser	(revision )
@@ -32,3 +32,4 @@
 org.apache.tika.parser.txt.TXTParser
 org.apache.tika.parser.video.FLVParser
 org.apache.tika.parser.xml.DcXMLParser
+org.apache.tika.parser.iwork.IWorkParser
Index: tika-parsers/src/test/java/org/apache/tika/parser/iwork/IWorkParserTest.java
===================================================================
--- tika-parsers/src/test/java/org/apache/tika/parser/iwork/IWorkParserTest.java	(revision )
+++ tika-parsers/src/test/java/org/apache/tika/parser/iwork/IWorkParserTest.java	(revision )
@@ -0,0 +1,69 @@
+package org.apache.tika.parser.iwork;
+
+import junit.framework.TestCase;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.sax.BodyContentHandler;
+import org.xml.sax.ContentHandler;
+
+import java.io.InputStream;
+
+/**
+ * 
+ */
+public class IWorkParserTest extends TestCase {
+
+  private IWorkParser iWorkParser;
+
+  @Override
+  protected void setUp() throws Exception {
+    iWorkParser = new IWorkParser();
+  }
+
+  public void testParseKeynote() throws Exception {
+    InputStream input = IWorkParserTest.class.getResourceAsStream("/test-documents/testKeynote.key");
+    Metadata metadata = new Metadata();
+    ContentHandler handler = new BodyContentHandler();
+    ParseContext parseContext = new ParseContext();
+
+    iWorkParser.parse(input, handler, metadata, parseContext);
+
+    assertEquals(6, metadata.size());
+    assertEquals("application/vnd.apple.keynote", metadata.get(Metadata.CONTENT_TYPE));
+    assertEquals("2", metadata.get(Metadata.SLIDE_COUNT));
+    assertEquals("1024", metadata.get(KeynoteExtractor.PRESENTATION_WIDTH));
+    assertEquals("768", metadata.get(KeynoteExtractor.PRESENTATION_HEIGHT));
+    assertEquals("Tika user", metadata.get(Metadata.AUTHOR));
+    assertEquals("Apache tika", metadata.get(Metadata.TITLE));
+
+    String content = handler.toString();
+    assertTrue(content.contains("A sample presentation"));
+    assertTrue(content.contains("For the Apache Tika project"));
+    assertTrue(content.contains("Slide 1"));
+    assertTrue(content.contains("Some random text for the sake of testability."));
+    assertTrue(content.contains("A nice comment"));
+    assertTrue(content.contains("A nice note"));
+  }
+
+  public void testParsePages() throws Exception {
+    InputStream input = IWorkParserTest.class.getResourceAsStream("/test-documents/testPages.pages");
+    Metadata metadata = new Metadata();
+    ContentHandler handler = new BodyContentHandler();
+    ParseContext parseContext = new ParseContext();
+
+    iWorkParser.parse(input, handler, metadata, parseContext);
+
+    assertEquals(51, metadata.size());
+    assertEquals("application/vnd.apple.pages", metadata.get(Metadata.CONTENT_TYPE));
+    assertEquals("Tika user", metadata.get(Metadata.AUTHOR));
+    assertEquals("Apache tika", metadata.get(Metadata.TITLE));
+    assertEquals("2010-05-09T21:34:38+0200", metadata.get(Metadata.CREATION_DATE));
+    assertEquals("2010-05-09T23:50:36+0200", metadata.get(Metadata.LAST_MODIFIED));
+    assertEquals("en", metadata.get(Metadata.LANGUAGE));
+    assertEquals("2", metadata.get(Metadata.PAGE_COUNT));
+
+    String content = handler.toString();
+    System.out.println(content);
+  }
+
+}
Index: tika-parsers/src/main/java/org/apache/tika/parser/iwork/NumbersExtractor.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/iwork/NumbersExtractor.java	(revision )
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/NumbersExtractor.java	(revision )
@@ -0,0 +1,7 @@
+package org.apache.tika.parser.iwork;
+
+/**
+ * 
+ */
+public class NumbersExtractor {
+}
Index: tika-parsers/src/main/java/org/apache/tika/parser/iwork/IWorkParser.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/iwork/IWorkParser.java	(revision )
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/IWorkParser.java	(revision )
@@ -0,0 +1,71 @@
+package org.apache.tika.parser.iwork;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * A parser for the IWork formats.
+ *
+ * Currently supported formats:
+ * <ol>
+ * <li>Keynote format version 2.x. Currently only tested with Keynote version 5.x
+ * <li>Pages format version 1.x. Currently only tested with Keynote version 4.0.x
+ * </ol>
+ */
+public class IWorkParser implements Parser {
+
+  private final static Set<MediaType> supportedTypes =
+          Collections.unmodifiableSet(
+                  new HashSet<MediaType>(Arrays.asList(
+                          MediaType.application("vnd.apple.keynote"),
+                          MediaType.application("vnd.apple.pages"),
+                          MediaType.application("vnd.apple.numbers")
+                  )));
+
+  public Set<MediaType> getSupportedTypes(ParseContext context) {
+    return supportedTypes;
+  }
+
+  public void parse(InputStream stream, ContentHandler handler, Metadata metadata, ParseContext context) throws IOException, SAXException, TikaException {
+    ZipInputStream zipInputStream = new ZipInputStream(stream);
+
+    ZipEntry entry;
+    while ((entry = zipInputStream.getNextEntry()) != null) {
+      if ("index.apxl".equals(entry.getName())) {
+        if (metadata.get(Metadata.CONTENT_TYPE) == null) {
+          metadata.set(Metadata.CONTENT_TYPE, "application/vnd.apple.keynote");
+        }
+        new KeynoteExtractor().parse(zipInputStream, handler, metadata, context);
+        break;
+      } else if ("index.xml".equals(entry.getName())) {
+        if (metadata.get(Metadata.CONTENT_TYPE) == null) {
+          metadata.set(Metadata.CONTENT_TYPE, "application/vnd.apple.pages");
+        }
+        new PagesExtractor().parse(zipInputStream, handler, metadata, context);
+        break;
+      } else if ("index.xml".equals(entry.getName())) {
+        // Numbers has index.xml as well. Therefore the filename cannot be used for detecting type.
+        // The xml file should be sniffed before determining the extractor
+        throw new UnsupportedOperationException("Cannot parse a Numbers document yet");
+      }
+    }
+  }
+
+  public void parse(InputStream stream, ContentHandler handler, Metadata metadata) throws IOException, SAXException, TikaException {
+    parse(stream, handler, metadata, new ParseContext());
+  }
+}
Index: tika-parsers/src/main/java/org/apache/tika/parser/iwork/AbstractIWorkExtractor.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/iwork/AbstractIWorkExtractor.java	(revision )
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/AbstractIWorkExtractor.java	(revision )
@@ -0,0 +1,28 @@
+package org.apache.tika.parser.iwork;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.io.CloseShieldInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.xml.XMLParser;
+import org.apache.tika.sax.OfflineContentHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
+ */
+public abstract class AbstractIWorkExtractor extends XMLParser {
+
+  @Override
+  public void parse(InputStream stream, ContentHandler handler, Metadata metadata, ParseContext context) throws IOException, SAXException, TikaException {
+    getSAXParser(context).parse(
+            new CloseShieldInputStream(stream),
+            new OfflineContentHandler(
+                    getContentHandler(handler, metadata)));
+  }
+
+}
Index: tika-parsers/src/test/java/org/apache/tika/parser/AutoDetectParserTest.java
===================================================================
--- tika-parsers/src/test/java/org/apache/tika/parser/AutoDetectParserTest.java	(revision 925321)
+++ tika-parsers/src/test/java/org/apache/tika/parser/AutoDetectParserTest.java	(revision )
@@ -16,15 +16,14 @@
  */
 package org.apache.tika.parser;
 
-import java.io.IOException;
-import java.io.InputStream;
-
+import junit.framework.TestCase;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.sax.BodyContentHandler;
 import org.xml.sax.ContentHandler;
 
-import junit.framework.TestCase;
+import java.io.IOException;
+import java.io.InputStream;
 
 public class AutoDetectParserTest extends TestCase {
 
@@ -34,6 +33,7 @@
     private static final String HTML       = "text/html";
     private static final String PDF        = "application/pdf";
     private static final String POWERPOINT = "application/vnd.ms-powerpoint";
+    private static final String KEYNOTE = "application/vnd.apple.keynote";
     private static final String RTF        = "application/rtf";
     private static final String PLAINTEXT  = "text/plain";
     private static final String WORD       = "application/msword";
@@ -123,6 +123,10 @@
         assertAutoDetect(resource, badResource, type, wrongMimeType, content);
     }
 
+    public void testKeynote() throws Exception {
+      assertAutoDetect("testKeynote.key", KEYNOTE, "A sample presentation");  
+    }
+
     public void testEpub() throws Exception {
         assertAutoDetect(
                 "testEPUB.epub", "application/epub+zip",
Index: tika-parsers/src/main/java/org/apache/tika/parser/iwork/KeynoteExtractor.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/iwork/KeynoteExtractor.java	(revision )
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/KeynoteExtractor.java	(revision )
@@ -0,0 +1,139 @@
+package org.apache.tika.parser.iwork;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ *
+ */
+public class KeynoteExtractor extends AbstractIWorkExtractor {
+
+  public final static String PRESENTATION_WIDTH = "slides-width";
+  public final static String PRESENTATION_HEIGHT = "slides-height";
+
+  @Override
+  protected ContentHandler getContentHandler(ContentHandler handler, Metadata metadata) {
+    return new KeynoteContentHandler(handler, metadata);
+  }
+
+  private static class KeynoteContentHandler extends DefaultHandler {
+
+    private final XHTMLContentHandler xhtml;
+    private final Metadata metadata;
+
+    private boolean inSlide = false;
+    private boolean inTheme = false;
+    private boolean inTitle = false;
+    private boolean inBody = false;
+
+    private boolean inMetadata = false;
+    private boolean inMetaDataTitle = false;
+    private boolean inMetaDataAuthors = false;
+
+    private boolean stickNote = false;
+    private boolean notes = false;
+
+    private boolean inParsableText = false;
+
+    private int numberOfSlides = 0;
+
+    private KeynoteContentHandler(ContentHandler handler, Metadata metadata) {
+      this.metadata = metadata;
+      xhtml = new XHTMLContentHandler(handler, metadata);
+    }
+
+    @Override
+    public void startDocument() throws SAXException {
+      xhtml.startDocument();
+    }
+
+    @Override
+    public void endDocument() throws SAXException {
+      xhtml.endDocument();
+      metadata.set(Metadata.SLIDE_COUNT, String.valueOf(numberOfSlides));
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+      if ("key:theme".equals(qName)) {
+        inTheme = true;
+      } else if ("key:slide".equals(qName)) {
+        inSlide = true;
+        numberOfSlides++;
+        xhtml.startElement("div");
+      } else if ("key:title-placeholder".equals(qName) && inSlide) {
+        inTitle = true;
+        xhtml.startElement("h1");
+      } else if ("sf:sticky-note".equals(qName) && inSlide) {
+        xhtml.startElement("p");
+      } else if ("key:notes".equals(qName) && inSlide) {
+        xhtml.startElement("p");
+      } else if ("key:body-placeholder".equals(qName) && inSlide) {
+        xhtml.startElement("p");
+        inBody = true;
+      } else if ("key:size".equals(qName) && !inTheme) {
+        String width = attributes.getValue("sfa:w");
+        String height = attributes.getValue("sfa:h");
+        metadata.set(PRESENTATION_WIDTH, width);
+        metadata.set(PRESENTATION_HEIGHT, height);
+      } else if ("sf:text-body".equals(qName)) {
+        inParsableText = true;
+      } else if ("key:metadata".equals(qName)) {
+        inMetadata = true;
+      } else if (inMetadata && "key:title".equals(qName)) {
+        inMetaDataTitle = true;
+      } else if (inMetadata && "key:authors".equals(qName)) {
+        inMetaDataAuthors = true;
+      } else if (inMetaDataTitle && "key:string".equals(qName)) {
+        metadata.set(Metadata.TITLE, attributes.getValue("sfa:string"));
+      } else if (inMetaDataAuthors && "key:string".equals(qName)) {
+        metadata.add(Metadata.AUTHOR, attributes.getValue("sfa:string"));
+      }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+      if ("key:theme".equals(qName)) {
+        inTheme = false;
+      } else if ("key:slide".equals(qName)) {
+        inSlide = false;
+        xhtml.endElement("div");
+      } else if ("key:title-placeholder".equals(qName) && inSlide) {
+        inTitle = false;
+        xhtml.endElement("h1");
+      } else if ("sf:sticky-note".equals(qName) && inSlide) {
+        xhtml.endElement("p");
+      } else if ("key:notes".equals(qName) && inSlide) {
+        xhtml.endElement("p");
+      } else if ("key:body-placeholder".equals(qName) && inSlide) {
+        xhtml.endElement("p");
+        inBody = false;
+      } else if ("sf:text-body".equals(qName)) {
+        inParsableText = false;
+      } else if ("key:metadata".equals(qName)) {
+        inMetadata = false;
+      } else if (inMetadata && "key:title".equals(qName)) {
+        inMetaDataTitle = false;
+      } else if (inMetadata && "key:authors".equals(qName)) {
+        inMetaDataAuthors = false;
+      }
+    }
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+      if (!inParsableText || !inSlide) {
+        return;
+      }
+
+      String text = new String(ch, start, length).trim();
+      if (text.length() != 0) {
+        xhtml.characters(text);
+      }
+    }
+  }
+
+}
Index: tika-parsers/src/main/java/org/apache/tika/parser/xml/XMLParser.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/xml/XMLParser.java	(revision 911195)
+++ tika-parsers/src/main/java/org/apache/tika/parser/xml/XMLParser.java	(revision )
@@ -16,18 +16,6 @@
  */
 package org.apache.tika.parser.xml;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.xml.XMLConstants;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.CloseShieldInputStream;
 import org.apache.tika.metadata.Metadata;
@@ -42,6 +30,17 @@
 import org.xml.sax.SAXNotRecognizedException;
 import org.xml.sax.SAXNotSupportedException;
 
+import javax.xml.XMLConstants;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * XML parser.
  * <p>
@@ -116,7 +115,7 @@
      * @return SAX parser
      * @throws TikaException if a SAX parser could not be created
      */
-    private SAXParser getSAXParser(ParseContext context)
+    protected SAXParser getSAXParser(ParseContext context)
             throws TikaException {
         SAXParser parser = context.get(SAXParser.class);
         if (parser instanceof SAXParser) {
Index: tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
===================================================================
--- tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml	(revision 911452)
+++ tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml	(revision )
@@ -556,6 +556,17 @@
   <mime-type type="application/vnd.apple.installer+xml">
     <glob pattern="*.mpkg"/>
   </mime-type>
+  <mime-type type="application/vnd.apple.keynote">
+    <sub-class-of type="application/zip"/>
+    <alias type="application/vnd.apple.pages"/>
+    <alias type="application/vnd.apple.numbers"/>
+    <magic priority="40">
+      <match value="0x504b0304140000000000" type="string" offset="0"/>
+    </magic>
+    <glob pattern="*.key"/>
+    <glob pattern="*.pages"/>
+    <glob pattern="*.numbers"/>
+  </mime-type>
   <mime-type type="application/vnd.arastra.swi">
     <glob pattern="*.swi"/>
   </mime-type>
Index: tika-parsers/src/main/java/org/apache/tika/parser/iwork/PagesExtractor.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/iwork/PagesExtractor.java	(revision )
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/PagesExtractor.java	(revision )
@@ -0,0 +1,214 @@
+package org.apache.tika.parser.iwork;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ */
+public class PagesExtractor extends AbstractIWorkExtractor {
+
+  @Override
+  protected ContentHandler getContentHandler(ContentHandler handler, Metadata metadata) {
+    return new PagesContentHandler(handler, metadata);
+  }
+
+  private final static class PagesContentHandler extends DefaultHandler {
+
+    private final XHTMLContentHandler xhtml;
+    private final Metadata metadata;
+
+    private boolean inMetaDataPart = false;
+    private boolean parseProperty = false;
+    private boolean inParsableText = false;
+    private int pageCount = 0;
+
+    private Map<String, List<List<String>>> tableData = new HashMap<String, List<List<String>>>();
+    private String activeTableId;
+    private int numberOfColumns = 0;
+    private List<String> activeRow = new ArrayList<String>();
+
+    private String metaDataLocalName;
+    private String metaDataQName;
+
+    private PagesContentHandler(ContentHandler contentHandler, Metadata metadata) {
+      xhtml = new XHTMLContentHandler(contentHandler, metadata);
+      this.metadata = metadata;
+    }
+
+    @Override
+    public void startDocument() throws SAXException {
+      xhtml.startDocument();
+    }
+
+    @Override
+    public void endDocument() throws SAXException {
+      metadata.set(Metadata.PAGE_COUNT, String.valueOf(pageCount));
+      if (pageCount > 0) {
+        xhtml.endElement("div");
+      }
+      xhtml.endDocument();
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+      if (parseProperty) {
+        String value = parsePrimitiveElementValue(qName, attributes);
+        if (value != null) {
+          String metaDataKey = resolveMetaDataKey(metaDataLocalName);
+          metadata.add(metaDataKey, value);
+        }
+      }
+
+      if ("sl:publication-info".equals(qName)) {
+        inMetaDataPart = true;
+      } else if ("sf:metadata".equals(qName)) {
+        inMetaDataPart = true;
+      } else if ("sf:page-start".equals(qName)) {
+        if (pageCount > 0) {
+          xhtml.endElement("div");
+        }
+        xhtml.startElement("div");
+        pageCount++;
+      } else if ("sf:p".equals(qName) && pageCount > 0) {
+        inParsableText = true;
+        xhtml.startElement("p");
+      } else if ("sf:attachment".equals(qName)) {
+        String kind = attributes.getValue("sf:kind");
+        if ("tabular-attachment".equals(kind)) {
+          activeTableId = attributes.getValue("sfa:ID");
+          tableData.put(activeTableId, new ArrayList<List<String>>());
+        }
+      } else if ("sf:attachment-ref".equals(qName)) {
+        String idRef = attributes.getValue("sfa:IDREF");
+        outputTable(idRef);
+      }
+
+      if (activeTableId != null) {
+        parseTableData(qName, attributes);
+      }
+
+      if (inMetaDataPart) {
+        metaDataLocalName = localName;
+        metaDataQName = qName;
+        parseProperty = true;
+      }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+      if (metaDataLocalName != null && metaDataLocalName.equals(localName)) {
+        metaDataLocalName = null;
+        parseProperty = false;
+      }
+
+      if ("sl:publication-info".equals(qName)) {
+        inMetaDataPart = false;
+      } else if ("sf:metadata".equals(qName)) {
+        inMetaDataPart = false;
+      } else if ("sf:p".equals(qName) && pageCount > 0) {
+        inParsableText = false;
+        xhtml.endElement("p");
+      } else if ("sf:attachment".equals(qName)) {
+        activeTableId = null;
+      }
+    }
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+      if (!inParsableText) {
+        return;
+      }
+
+      String text = new String(ch, start, length).trim();
+      if (text.length() != 0) {
+        xhtml.characters(text);
+      }
+    }
+
+    private void parseTableData(String qName, Attributes attributes) {
+      if ("sf:grid".equals(qName)) {
+        String numberOfColumns = attributes.getValue("sf:numcols");
+        this.numberOfColumns = Integer.parseInt(numberOfColumns);
+      } else if ("sf:ct".equals(qName)) {
+        activeRow.add(attributes.getValue("sfa:s"));
+
+        if (activeRow.size() >= 3) {
+          tableData.get(activeTableId).add(activeRow);
+          activeRow = new ArrayList<String>();
+        }
+      }
+    }
+
+    private void outputTable(String idRef) throws SAXException {
+      List<List<String>> tableData = this.tableData.get(idRef);
+      if (tableData != null) {
+        xhtml.startElement("table");
+        for (List<String> row : tableData) {
+          xhtml.startElement("tr");
+          for (String cell : row) {
+            xhtml.element("td", cell);
+          }
+          xhtml.endElement("tr");
+        }
+        xhtml.endElement("table");
+      }
+    }
+
+    /**
+     * Returns a resolved key that is common in other document types or returns the specified metaDataLocalName if no
+     * common key could be found.
+     *
+     * @param metaDataLocalName The localname of the element containing metadata
+     * @return a resolved key that is common in other document types
+     */
+    private String resolveMetaDataKey(String metaDataLocalName) {
+      String metaDataKey = metaDataLocalName;
+      if ("sf:authors".equals(metaDataQName)) {
+        metaDataKey = Metadata.AUTHOR;
+      } else if ("sf:title".equals(metaDataQName)) {
+        metaDataKey = Metadata.TITLE;
+      } else if ("sl:SLCreationDateProperty".equals(metaDataQName)) {
+        metaDataKey = Metadata.CREATION_DATE;
+      } else if ("sl:SLLastModifiedDateProperty".equals(metaDataQName)) {
+        metaDataKey = Metadata.LAST_MODIFIED;
+      } else if ("sl:language".equals(metaDataQName)) {
+        metaDataKey = Metadata.LANGUAGE;
+      }
+      return metaDataKey;
+    }
+
+    /**
+     * Returns the value of a primitive element e.g.:
+     * <sl:number sfa:number="0" sfa:type="f"/> - the number attribute
+     * <sl:string sfa:string="en"/> = the string attribute
+     * <p/>
+     * Returns <code>null</code> if the value could not be extracted from the list of attributes.
+     *
+     * @param qName      The fully qualified name of the element containing the value to extract
+     * @param attributes The list of attributes of which one contains the value to be extracted
+     * @return the value of a primitive element
+     */
+    private String parsePrimitiveElementValue(String qName, Attributes attributes) {
+      if ("sl:string".equals(qName) || "sf:string".equals(qName)) {
+        return attributes.getValue("sfa:string");
+      } else if ("sl:number".equals(qName)) {
+        return attributes.getValue("sfa:number");
+      } else if ("sl:date".equals(qName)) {
+        return attributes.getValue("sf:val");
+      }
+
+      return null;
+    }
+  }
+
+}
