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 943569)
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/IWorkParser.java	(revision )
@@ -42,7 +42,8 @@
  * 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
+ * <li>Pages format version 1.x. Currently only tested with Pages version 4.0.x
+ * <li>Numbers format version 1.x. Currently only tested with Numbers version 2.0.x
  * </ol>
  */
 public class IWorkParser implements Parser {
@@ -81,20 +82,16 @@
                         new OfflineContentHandler(
                                 new KeynoteContentHandler(xhtml, metadata)));
             } else if ("index.xml".equals(entry.getName())) {
-                // TODO: 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
+                // Numbers and Pages have both index.xml as file, so the appropriate content handler can only be
+                // selected based on the content in the file. In this case the content handler is selected
+                // based on root element.
+                IWorkRootElementDetectContentHandler detectHandler = new IWorkRootElementDetectContentHandler(metadata);
+                detectHandler.addHandler("sl:document", new PagesContentHandler(xhtml, metadata), "application/vnd.apple.pages");
+                detectHandler.addHandler("ls:document", new NumbersContentHandler(xhtml, metadata), "application/vnd.apple.numbers");
 
-                if (metadata.get(Metadata.CONTENT_TYPE) == null) {
-                    metadata.set(
-                            Metadata.CONTENT_TYPE,
-                            "application/vnd.apple.pages");
-                }
-
                 context.getSAXParser().parse(
                         new CloseShieldInputStream(zip),
-                        new OfflineContentHandler(
-                                new PagesContentHandler(xhtml, metadata)));
+                        new OfflineContentHandler(detectHandler));
             }
             entry = zip.getNextEntry();
         }
Index: tika-parsers/src/main/java/org/apache/tika/parser/iwork/IWorkRootElementDetectContentHandler.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/iwork/IWorkRootElementDetectContentHandler.java	(revision )
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/IWorkRootElementDetectContentHandler.java	(revision )
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tika.parser.iwork;
+
+import org.apache.tika.metadata.Metadata;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A handler that detects based on the root element which encapsulated handler to use.
+ *
+ * If during parsing no handler can be matched to the rootElement an exception is thrown.
+ */
+class IWorkRootElementDetectContentHandler extends DefaultHandler {
+
+    private final Map<String, DefaultHandler> handlers = new HashMap<String, DefaultHandler>();
+    private final Map<String, String> contentTypes = new HashMap<String, String>();
+
+    private DefaultHandler chosenHandler;
+    private final Metadata metadata;
+
+    IWorkRootElementDetectContentHandler(Metadata metadata) {
+        this.metadata = metadata;
+    }
+
+    /**
+     * Adds a handler to this auto detect parser that handles parsing when the specified rootElement is encountered.
+     *
+     * @param rootElement The root element the identifies the specified handler
+     * @param handler The handler that does the actual parsing of the auto detected content
+     * @param contentType The content type that belongs to the auto detected content
+     */
+    public void addHandler(String rootElement, DefaultHandler handler, String contentType) {
+        handlers.put(rootElement, handler);
+        contentTypes.put(rootElement, contentType);
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+        if (chosenHandler != null) {
+            chosenHandler.startElement(uri, localName, qName, attributes);
+            return;
+        }
+
+        DefaultHandler candidateHandler = handlers.get(qName);
+        if (candidateHandler != null) {
+            chosenHandler = candidateHandler;
+            if (metadata.get(Metadata.CONTENT_TYPE) == null) {
+                metadata.add(Metadata.CONTENT_TYPE, contentTypes.get(qName));
+            }
+            chosenHandler.startElement(uri, localName, qName, attributes);
+        } else {
+            throw new RuntimeException("Could not find handler to parse document based on root element");
+        }
+    }
+
+    @Override
+    public void endDocument() throws SAXException {
+        chosenHandler.endDocument();
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        chosenHandler.endElement(uri, localName, qName);
+    }
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        chosenHandler.characters(ch, start, length);
+    }
+
+    @Override
+    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+        chosenHandler.ignorableWhitespace(ch, start, length);
+    }
+
+    @Override
+    public void processingInstruction(String target, String data) throws SAXException {
+        chosenHandler.processingInstruction(target, data);
+    }
+
+    @Override
+    public void skippedEntity(String name) throws SAXException {
+        chosenHandler.skippedEntity(name);
+    }
+
+    @Override
+    public void warning(SAXParseException e) throws SAXException {
+        chosenHandler.warning(e);
+    }
+
+    @Override
+    public void error(SAXParseException e) throws SAXException {
+        chosenHandler.error(e);
+    }
+
+    @Override
+    public void fatalError(SAXParseException e) throws SAXException {
+        chosenHandler.fatalError(e);
+    }
+}
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 948452)
+++ tika-parsers/src/test/java/org/apache/tika/parser/iwork/IWorkParserTest.java	(revision )
@@ -25,7 +25,7 @@
 import java.io.InputStream;
 
 /**
- * Tests if the different iwork parsers parse the content and metadata properly. 
+ * Tests if the different iwork parsers parse the content and metadata properly.
  */
 public class IWorkParserTest extends TestCase {
 
@@ -110,4 +110,33 @@
         assertTrue(content.contains("Extensible Markup Language")); // ...
     }
 
+    public void testParseNumbers() throws Exception {
+        InputStream input = IWorkParserTest.class.getResourceAsStream("/test-documents/testNumbers.numbers");
+        Metadata metadata = new Metadata();
+        ContentHandler handler = new BodyContentHandler();
+        ParseContext parseContext = new ParseContext();
+
+        iWorkParser.parse(input, handler, metadata, parseContext);
+
+        String content = handler.toString();
+
+        assertEquals(9, metadata.size());
+        assertEquals("2", metadata.get(Metadata.PAGE_COUNT));
+        assertEquals("Tika User", metadata.get(Metadata.AUTHOR));
+        assertEquals("Account checking", metadata.get(Metadata.TITLE));
+        assertEquals("a comment", metadata.get(Metadata.COMMENT));
+
+        assertTrue(content.contains("Category"));
+        assertTrue(content.contains("Home"));
+        assertTrue(content.contains("-226"));
+        assertTrue(content.contains("-137.5"));
+        assertTrue(content.contains("Checking Account: 300545668"));
+        assertTrue(content.contains("4650"));
+        assertTrue(content.contains("Credit Card"));
+        assertTrue(content.contains("Groceries"));
+        assertTrue(content.contains("-210"));
+        assertTrue(content.contains("Food"));
+        assertTrue(content.contains("Try adding your own account transactions to this table."));
-}
+    }
+
+}
Index: tika-parsers/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java
===================================================================
--- tika-parsers/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java	(revision )
+++ tika-parsers/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java	(revision )
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.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.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class NumbersContentHandler extends DefaultHandler {
+
+    private final XHTMLContentHandler xhtml;
+    private final Metadata metadata;
+
+    private boolean inSheet = false;
+
+    private boolean inText = false;
+    private boolean parseText = false;
+
+    private boolean inMetadata = false;
+    private String metadataKey;
+    private String metadataPropertyQName;
+
+    private boolean inTable = false;
+    private int numberOfSheets = 0;
+    private int numberOfColumns = -1;
+    private int currentColumn = 0;
+
+    private Map<String, String> menuItems = new HashMap<String, String>();
+    private String currentMenuItemId;
+
+    NumbersContentHandler(XHTMLContentHandler xhtml, Metadata metadata) {
+        this.xhtml = xhtml;
+        this.metadata = metadata;
+    }
+
+    @Override
+    public void endDocument() throws SAXException {
+        metadata.set(Metadata.PAGE_COUNT, String.valueOf(numberOfSheets));
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+        if ("ls:workspace".equals(qName)) {
+            inSheet = true;
+            numberOfSheets++;
+            xhtml.startElement("div");
+            String sheetName = attributes.getValue("ls:workspace-name");
+            metadata.add("sheetNames", sheetName);
+        }
+
+        if ("sf:text".equals(qName)) {
+            inText = true;
+            xhtml.startElement("p");
+        }
+
+        if ("sf:p".equals(qName)) {
+            parseText = true;
+        }
+
+        if ("sf:metadata".equals(qName)) {
+            inMetadata = true;
+            return;
+        }
+
+        if (inMetadata && metadataKey == null) {
+            metadataKey = resolveMetadataKey(localName);
+            metadataPropertyQName = qName;
+        }
+
+        if (inMetadata && metadataKey != null && "sf:string".equals(qName)) {
+            metadata.add(metadataKey, attributes.getValue("sfa:string"));
+        }
+
+        if (!inSheet) {
+            return;
+        }
+
+        if ("sf:tabular-model".equals(qName)) {
+            inTable = true;
+            xhtml.startElement("table");
+            xhtml.startElement("tr");
+            currentColumn = 0;
+//            String tableName = attributes.getValue("sf:name");
+        }
+
+        if ("sf:menu-choices".equals(qName)) {
+            menuItems = new HashMap<String, String>();
+        }
+
+        if (inTable && "sf:grid".equals(qName)) {
+            numberOfColumns = Integer.parseInt(attributes.getValue("sf:numcols"));
+        }
+
+        if (menuItems != null && "sf:t".equals(qName)) {
+            currentMenuItemId = attributes.getValue("sfa:ID");
+        }
+
+        if (currentMenuItemId != null && "sf:ct".equals(qName)) {
+            menuItems.put(currentMenuItemId, attributes.getValue("sfa:s"));
+        }
+
+        if (inTable && "sf:ct".equals(qName)) {
+            if (currentColumn >= numberOfColumns) {
+                currentColumn = 0;
+                xhtml.endElement("tr");
+                xhtml.startElement("tr");
+            }
+
+            xhtml.element("td", attributes.getValue("sfa:s"));
+            currentColumn++;
+        }
+
+        if (inTable && ("sf:n".equals(qName) || "sf:rn".equals(qName))) {
+            if (currentColumn >= numberOfColumns) {
+                currentColumn = 0;
+                xhtml.endElement("tr");
+                xhtml.startElement("tr");
+            }
+
+            xhtml.element("td", attributes.getValue("sf:v"));
+            currentColumn++;
+        }
+
+        if (inTable && "sf:proxied-cell-ref".equals(qName)) {
+            if (currentColumn >= numberOfColumns) {
+                currentColumn = 0;
+                xhtml.endElement("tr");
+                xhtml.startElement("tr");
+            }
+
+            xhtml.element("td", menuItems.get(attributes.getValue("sfa:IDREF")));
+            currentColumn++;
+        }
+    }
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        if (!parseText) {
+            return;
+        }
+
+        String text = new String(ch, start, length).trim();
+        if (text.length() > 0) {
+            xhtml.characters(text);
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        if ("ls:workspace".equals(qName)) {
+            inSheet = false;
+            xhtml.endElement("div");
+        }
+
+        if ("sf:text".equals(qName)) {
+            inText = false;
+            xhtml.endElement("p");
+        }
+
+        if ("sf:p".equals(qName)) {
+            parseText = false;
+        }
+
+        if ("sf:metadata".equals(qName)) {
+            inMetadata = false;
+        }
+
+        if (inMetadata && qName.equals(metadataPropertyQName)) {
+            metadataPropertyQName = null;
+            metadataKey = null;
+        }
+
+        if (!inSheet) {
+            return;
+        }
+
+        if ("sf:menu-choices".equals(qName)) {
+        }
+
+        if ("sf:tabular-model".equals(qName)) {
+            inTable = false;
+            xhtml.endElement("tr");
+            xhtml.endElement("table");
+        }
+
+        if (currentMenuItemId != null && "sf:t".equals(qName)) {
+            currentMenuItemId = null;
+        }
+    }
+
+    private String resolveMetadataKey(String localName) {
+        if ("authors".equals(localName)) {
+            return Metadata.AUTHOR;
+        }
+
+        return localName;
+    }
+}
