Dust-off SkXMLParser

Hook up SkXMLParser to Expat, such that it can actually parse, err,
XML.

Add a trivial unit test.

R=robertphillips@google.com,reed@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2142893006

Review-Url: https://codereview.chromium.org/2142893006
diff --git a/gyp/expat.gyp b/gyp/expat.gyp
index 90f7b02..90e4e44 100644
--- a/gyp/expat.gyp
+++ b/gyp/expat.gyp
@@ -12,9 +12,12 @@
         'cflags': [ '-Wno-missing-field-initializers' ],
         'xcode_settings': { 'WARNING_CFLAGS': [ '-Wno-missing-field-initializers', ], },
         'msvs_disabled_warnings': [4244],
-        'defines': [ 'HAVE_EXPAT_CONFIG_H' ],
+        'defines': [
+            'HAVE_EXPAT_CONFIG_H',
+            'XML_STATIC', # Compile for static linkage.
+        ],
         'include_dirs': [
-             '../third_party/externals/expat',
+            '../third_party/externals/expat',
         ],
         'sources': [
             '../third_party/externals/expat/lib/xmlparse.c',
@@ -22,7 +25,12 @@
             '../third_party/externals/expat/lib/xmltok.c',
         ],
         'direct_dependent_settings': {
-            'include_dirs': [ '../third_party/externals/expat/lib' ],
+            'include_dirs': [
+                '../third_party/externals/expat/lib',
+            ],
+            'defines': [
+                'XML_STATIC',  # Tell dependants to expect static linkage.
+            ],
         },
     }]
 }
diff --git a/gyp/xml.gyp b/gyp/xml.gyp
index 8060076..f7cb429 100644
--- a/gyp/xml.gyp
+++ b/gyp/xml.gyp
@@ -14,21 +14,22 @@
         'expat.gyp:expat',
       ],
       'include_dirs': [
-        '../include/private',
-        '../include/xml',
+        '<(skia_include_path)/private',
+        '<(skia_include_path)/xml',
       ],
       'sources': [
-        '../include/xml/SkDOM.h',
-        '../include/xml/SkXMLParser.h',
-        '../include/xml/SkXMLWriter.h',
+        '<(skia_include_path)/xml/SkDOM.h',
+        '<(skia_include_path)/xml/SkXMLParser.h',
+        '<(skia_include_path)/xml/SkXMLWriter.h',
 
-        '../src/xml/SkDOM.cpp',
-        '../src/xml/SkXMLParser.cpp',
-        '../src/xml/SkXMLPullParser.cpp',
-        '../src/xml/SkXMLWriter.cpp',
+        '<(skia_src_path)/xml/SkDOM.cpp',
+        '<(skia_src_path)/xml/SkXMLParser.cpp',
+        '<(skia_src_path)/xml/SkXMLPullParser.cpp',
+        '<(skia_src_path)/xml/SkXMLWriter.cpp',
       ],
       'sources!': [
-          '../src/xml/SkXMLPullParser.cpp', #if 0 around class decl in header
+          # time to kill this?
+          '<(skia_src_path)/xml/SkXMLPullParser.cpp', #if 0 around class decl in header
       ],
       'direct_dependent_settings': {
         'include_dirs': [
diff --git a/include/xml/SkDOM.h b/include/xml/SkDOM.h
index 14d79cf..2a6ccc4 100644
--- a/include/xml/SkDOM.h
+++ b/include/xml/SkDOM.h
@@ -13,14 +13,16 @@
 #include "../private/SkTemplates.h"
 #include "SkChunkAlloc.h"
 #include "SkScalar.h"
+#include "SkTypes.h"
 
 struct SkDOMNode;
 struct SkDOMAttr;
 
 class SkDOMParser;
+class SkStream;
 class SkXMLParser;
 
-class SkDOM {
+class SK_API SkDOM : public SkNoncopyable {
 public:
     SkDOM();
     ~SkDOM();
@@ -31,6 +33,7 @@
     /** Returns null on failure
     */
     const Node* build(const char doc[], size_t len);
+    const Node* build(SkStream&);
     const Node* copy(const SkDOM& dom, const Node* node);
 
     const Node* getRootNode() const;
@@ -85,15 +88,13 @@
     };
 
     SkDEBUGCODE(void dump(const Node* node = NULL, int tabLevel = 0) const;)
-    SkDEBUGCODE(static void UnitTest();)
 
 private:
     SkChunkAlloc               fAlloc;
     Node*                      fRoot;
     SkAutoTDelete<SkDOMParser> fParser;
 
-    friend class AttrIter;
-    friend class SkDOMParser;
+    typedef SkNoncopyable INHERITED;
 };
 
 #endif
diff --git a/src/xml/SkDOM.cpp b/src/xml/SkDOM.cpp
index 15b458e..78cf6f0 100644
--- a/src/xml/SkDOM.cpp
+++ b/src/xml/SkDOM.cpp
@@ -310,10 +310,9 @@
     int                     fLevel;
 };
 
-const SkDOM::Node* SkDOM::build(const char doc[], size_t len)
-{
+const SkDOM::Node* SkDOM::build(SkStream& docStream) {
     SkDOMParser parser(&fAlloc);
-    if (!parser.parse(doc, len))
+    if (!parser.parse(docStream))
     {
         SkDEBUGCODE(SkDebugf("xml parse error, line %d\n", parser.fParserError.getLineNumber());)
         fRoot = nullptr;
@@ -324,6 +323,11 @@
     return fRoot;
 }
 
+const SkDOM::Node* SkDOM::build(const char doc[], size_t len) {
+    SkMemoryStream docStream(doc, len);
+    return this->build(docStream);
+}
+
 ///////////////////////////////////////////////////////////////////////////
 
 static void walk_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLParser* parser)
@@ -475,40 +479,4 @@
     xmlWriter.writeDOM(*this, node, false);
 }
 
-void SkDOM::UnitTest()
-{
-#ifdef SK_SUPPORT_UNITTEST
-    static const char gDoc[] =
-        "<root a='1' b='2'>"
-            "<elem1 c='3' />"
-            "<elem2 d='4' />"
-            "<elem3 e='5'>"
-                "<subelem1/>"
-                "<subelem2 f='6' g='7'/>"
-            "</elem3>"
-            "<elem4 h='8'/>"
-        "</root>"
-        ;
-
-    SkDOM   dom;
-
-    SkASSERT(dom.getRootNode() == nullptr);
-
-    const Node* root = dom.build(gDoc, sizeof(gDoc) - 1);
-    SkASSERT(root && dom.getRootNode() == root);
-
-    const char* v = dom.findAttr(root, "a");
-    SkASSERT(v && !strcmp(v, "1"));
-    v = dom.findAttr(root, "b");
-    SkASSERT(v && !strcmp(v, "2"));
-    v = dom.findAttr(root, "c");
-    SkASSERT(v == nullptr);
-
-    SkASSERT(dom.getFirstChild(root, "elem1"));
-    SkASSERT(!dom.getFirstChild(root, "subelem1"));
-
-    dom.dump();
-#endif
-}
-
 #endif
diff --git a/src/xml/SkXMLParser.cpp b/src/xml/SkXMLParser.cpp
index 11f4c6b..77903cb 100644
--- a/src/xml/SkXMLParser.cpp
+++ b/src/xml/SkXMLParser.cpp
@@ -5,7 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "expat.h"
 
+#include "SkStream.h"
 #include "SkXMLParser.h"
 
 static char const* const gErrorStrings[] = {
@@ -47,9 +49,32 @@
     fNativeCode = -1;
 }
 
-
 ////////////////
 
+namespace {
+
+const XML_Memory_Handling_Suite sk_XML_alloc = {
+    sk_malloc_throw,
+    sk_realloc_throw,
+    sk_free
+};
+
+void XMLCALL start_element_handler(void *data, const char* tag, const char** attributes) {
+    SkXMLParser* parser = static_cast<SkXMLParser*>(data);
+
+    parser->startElement(tag);
+
+    for (size_t i = 0; attributes[i]; i += 2) {
+        parser->addAttribute(attributes[i], attributes[i + 1]);
+    }
+}
+
+void XMLCALL end_element_handler(void* data, const char* tag) {
+    static_cast<SkXMLParser*>(data)->endElement(tag);
+}
+
+} // anonymous namespace
+
 SkXMLParser::SkXMLParser(SkXMLParserError* parserError) : fParser(nullptr), fError(parserError)
 {
 }
@@ -60,12 +85,45 @@
 
 bool SkXMLParser::parse(SkStream& docStream)
 {
-    return false;
+    SkAutoTCallVProc<skstd::remove_pointer_t<XML_Parser>, XML_ParserFree>
+    parser(XML_ParserCreate_MM(nullptr, &sk_XML_alloc, nullptr));
+    if (!parser) {
+        SkDebugf("could not create XML parser\n");
+        return false;
+    }
+
+    XML_SetUserData(parser, this);
+    XML_SetElementHandler(parser, start_element_handler, end_element_handler);
+
+    static const int kBufferSize = 512 SkDEBUGCODE( - 507);
+    bool done = false;
+    do {
+        void* buffer = XML_GetBuffer(parser, kBufferSize);
+        if (!buffer) {
+            SkDebugf("could not buffer enough to continue\n");
+            return false;
+        }
+
+        size_t len = docStream.read(buffer, kBufferSize);
+        done = docStream.isAtEnd();
+        XML_Status status = XML_ParseBuffer(parser, SkToS32(len), done);
+        if (XML_STATUS_ERROR == status) {
+            XML_Error error = XML_GetErrorCode(parser);
+            int line = XML_GetCurrentLineNumber(parser);
+            int column = XML_GetCurrentColumnNumber(parser);
+            const XML_LChar* errorString = XML_ErrorString(error);
+            SkDebugf("parse error @%d:%d: %d (%s).\n", line, column, error, errorString);
+            return false;
+        }
+    } while (!done);
+
+    return true;
 }
 
 bool SkXMLParser::parse(const char doc[], size_t len)
 {
-    return false;
+    SkMemoryStream docStream(doc, len);
+    return this->parse(docStream);
 }
 
 void SkXMLParser::GetNativeErrorString(int error, SkString* str)
diff --git a/tests/SkDOMTest.cpp b/tests/SkDOMTest.cpp
new file mode 100644
index 0000000..aca2c48
--- /dev/null
+++ b/tests/SkDOMTest.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDOM.h"
+#include "Test.h"
+
+DEF_TEST(SkDOM_test, r) {
+    static const char gDoc[] =
+        "<root a='1' b='2'>"
+            "<elem1 c='3' />"
+            "<elem2 d='4' />"
+            "<elem3 e='5'>"
+                "<subelem1/>"
+                "<subelem2 f='6' g='7'/>"
+            "</elem3>"
+            "<elem4 h='8'/>"
+        "</root>"
+        ;
+
+    SkDOM   dom;
+    REPORTER_ASSERT(r, !dom.getRootNode());
+
+    const SkDOM::Node* root = dom.build(gDoc, sizeof(gDoc) - 1);
+    REPORTER_ASSERT(r, root && dom.getRootNode() == root);
+
+    const char* v = dom.findAttr(root, "a");
+    REPORTER_ASSERT(r, v && !strcmp(v, "1"));
+    v = dom.findAttr(root, "b");
+    REPORTER_ASSERT(r, v && !strcmp(v, "2"));
+    v = dom.findAttr(root, "c");
+    REPORTER_ASSERT(r, v == nullptr);
+
+    REPORTER_ASSERT(r, dom.getFirstChild(root, "elem1"));
+    REPORTER_ASSERT(r, !dom.getFirstChild(root, "subelem1"));
+}