diff --git a/FEATURE.md b/FEATURE.md new file mode 100644 index 0000000..5ea49da --- /dev/null +++ b/FEATURE.md @@ -0,0 +1,8 @@ +## FEATURES + +# xml2src + +Write a SAX parser to handle the wow.xml files for addons. + +http://www.jelks.nu/XML/xmlebnf.html + diff --git a/src/scripts/runmany.sh b/src/scripts/runmany.sh index 2d80c30..a20d6a3 100755 --- a/src/scripts/runmany.sh +++ b/src/scripts/runmany.sh @@ -8,11 +8,12 @@ for n in $(seq -f "%05g" 9999 1) ; do if [ ! "$?" == "0" ]; then cp target/reports/antout.txt target/reports/antOut$n.txt #mv $reportFile target/reports/testOut$n.xml - ls -alt target/reports/testOut$n.xml + ls -alt $reportFile until $(~/Scripts/checkFileChanged.sh ./test/test.lua); do sleep 1 done else - ls -alt $reportFile + #ls -alt $reportFile + sleep 1 fi done diff --git a/src/wowStubs.lua b/src/wowStubs.lua index bb99f94..f80bb2e 100644 --- a/src/wowStubs.lua +++ b/src/wowStubs.lua @@ -1730,10 +1730,170 @@ function C_ChatInfo.SendAddonMessage() return true end ------------------------------------------ --- XML functions +-- A SAX parser takes a content handler, which provides these methods: +-- startDocument() -- called at the start of the Document +-- endDocument() -- called at the end of the Document +-- startElement( tagName, attrs ) -- with each tag start. attrs is a table of attributes and values +-- endElement( tagName ) -- when a tag ends +-- characters( char ) -- for each character not being in a tag +-- The parser calls each of these methods as these events happen. + +-- A SAX parser defines a parser (created with makeParser) +-- a Parser has a ContentHandler assigned +-- a Parser also defines these methods: +-- setContentHandler( contentHandler ) +-- setFeature() -- prob not going to implement +-- parse( text ) +-- parse( file ) + +-- https://www.w3schools.com/xml/xml_elements.asp +-- https://www.w3schools.com/xml/xml_syntax.asp + + +contentHandler = {} +-- normally the contentHandler is an object, where data structures are created in the new object. +function contentHandler.startDocument( this ) +end +function contentHandler.endDocument( this ) +end +function contentHandler.startElement( this, tagName, attrs ) +end +function contentHandler.endElement( this, tagName ) +end +function contentHandler.characters( this, char ) +end + +saxParser = {} +-- SAX Parser +-- interface +function saxParser.makeParser() + -- make a parser. This is probably intended to be a factory function + return saxParser +end +function saxParser.setContentHandler( contentHandlerIn ) + -- takes a table + saxParser.contentHandler = contentHandlerIn +end +function saxParser.setFeature() + -- research this +end +function saxParser.parse( fileIn ) + f = io.open( fileIn, "r" ) + if f then fileIn = f:read( "*all" ) end -- read the contents of the file + + -- call the startDocument method for the given contentHandler + if saxParser.contentHandler and saxParser.contentHandler.startDocument then + saxParser.contentHandler:startDocument() + end + + -- loop through each char + State = { + Outside = { 0 }, -- outside of a tag + ElementName = { 1 }, -- When to parse for a name + InElement = { 2 }, -- In the element + } + currentState = State.Outside + elementDepth = {} -- table of current element depth + elementName = "" + chars = "" + + while( #fileIn > 0 ) do + -- print( currentState[1].."\t"..#fileIn, "fileIn: "..string.sub( fileIn, 1, 60 ) ) + c = string.sub( fileIn, 1, 1 ) + n = string.sub( fileIn, 2, 2 ) + handled = false + if currentState == State.Outside then + if c == "<" then + if n == "?" then + local endProlog = string.find( fileIn, "?>" ) + if endProlog then + fileIn = string.sub( fileIn, endProlog+2 ) + end + elseif n == "!" then + local endComment = string.find( fileIn, "-->" ) + if endComment then + fileIn = string.sub( fileIn, endComment+3 ) + end + else + currentState = State.ElementName + elementName = "" + fileIn = string.sub( fileIn, 2 ) + end + else + saxParser.contentHandler:characters( c ) + fileIn = string.sub( fileIn, 2 ) + end + elseif currentState == State.ElementName then + tagStart, tagEnd, tagName = string.find( fileIn, "^([%a_][%a%d-_.]*)" ) + if tagStart then + elementName = tagName + attributes = {} + currentState = State.InElement + fileIn = string.sub( fileIn, tagEnd + 1 ) + end + tagStart, tagEnd, tagName = string.find( fileIn, "^/([%a_][%a%d-_.]*)" ) + if tagStart then + elementName = tagName + -- print( "Fire endElement( "..tagName.." )" ) + depthElement = table.remove( elementDepth ) + saxParser.contentHandler:endElement( tagName ) + if depthElement ~= elementName then + fail( "ERROR: Closing "..elementName.." is not the expected element to close; "..depthElement.." is expected." ) + end + currentState = State.Outside + fileIn = string.sub( fileIn, tagEnd + 2 ) + end + elseif currentState == State.InElement then + attribStart, attribEnd, key, value = string.find( fileIn, "^%s*(%S+)%s*=%s*[\"\'](.-)[\"\']" ) + if attribStart then + attributes[key] = value + fileIn = string.sub( fileIn, attribEnd+1 ) + elseif c == " " then + fileIn = string.sub( fileIn, 2 ) + elseif c == ">" or n == ">" then + -- print( "Fire startElement( "..elementName.." )" ) + -- print( "\twith attributes: ") + -- for k,v in pairs( attributes ) do + -- print( "\t\t"..k..":="..v ) + -- end + table.insert( elementDepth, elementName ) + saxParser.contentHandler:startElement( elementName, attributes ) + currentState = State.Outside + if c == "/" and n == ">" then + -- print( "Fire endElement( "..elementName.." )" ) + depthElement = table.remove( elementDepth ) + saxParser.contentHandler:endElement( elementName ) + if depthElement ~= elementName then + fail( "ERROR: Closing "..elementName.." is not the expected element to close; "..depthElement.." is expected." ) + end + currentState = State.Outside + end + fileIn = string.sub( fileIn, (n==">" and 3 or 2) ) + end + end + -- print( "elementDepth: "..table.concat( elementDepth, "\t" ) ) + end + + -- call the endDocument method for the given contentHandler + if saxParser.contentHandler and saxParser.contentHandler.endDocument then + saxParser.contentHandler:endDocument() + end +end function ParseXML( xmlFile ) - print("parse: "..xmlFile ) + ch = contentHandler + ch.startElement = function( self, tagIn, attribs ) + if _G["Create"..tagIn] then + if attribs.name then + _G[attribs.name] = _G["Create"..tagIn]( attribs.name ) + _G[attribs.name].framename = attribs.name + else + fail("A "..tagIn.." needs a name") + end + end + end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( xmlFile ) end ----------------------------------------- diff --git a/test/test.lua b/test/test.lua index 2201f95..6a68de9 100644 --- a/test/test.lua +++ b/test/test.lua @@ -1062,13 +1062,173 @@ function CreateFile( filename, contents ) return tocFile end function test.testTOC_() - CreateFile( "test.xml", "\n\n" ) + CreateFile( "test.xml", "\n\n" ) CreateFile( "test.lua", "print(\"hi\")\n") generatedFile = CreateFile( "test.toc", "test.lua\ntest.xml\n" ) ParseTOC( generatedFile ) end +----- Sax tests +function test.before_testContentHandler() + originalContentHandler = {} + for k,v in pairs( contentHandler ) do + originalContentHandler[k] = v + end +end +function test.after_testContentHandler() + contentHandler = {} + for k,v in pairs( originalContentHandler ) do + contentHandler[k] = v + end +end +function test.testContentHandler_hasStartDocument() + test.before_testContentHandler() + assertTrue( contentHandler.startDocument ) + test.after_testContentHandler() +end +function test.testContentHandler_hasEndDocument() + test.before_testContentHandler() + assertTrue( contentHandler.endDocument ) + test.after_testContentHandler() +end +function test.testContentHandler_hasStartElement() + test.before_testContentHandler() + assertTrue( contentHandler.startElement ) + test.after_testContentHandler() +end +function test.testContentHandler_hasEndElement() + test.before_testContentHandler() + assertTrue( contentHandler.endElement ) + test.after_testContentHandler() +end +function test.testContentHandler_hasCharacters() + test.before_testContentHandler() + assertTrue( contentHandler.characters ) + test.after_testContentHandler() +end + + +function test.before_testSax() + originalContentHandler = {} + for k,v in pairs( contentHandler ) do + originalContentHandler[k] = v + end +end +function test.after_testSax() + contentHandler = {} + for k,v in pairs( originalContentHandler ) do + contentHandler[k] = v + end +end +function test.testSAX_MakeParser() + test.before_testSax() + assertTrue( saxParser.makeParser() ) + test.after_testSax() +end +function test.testSAX_setContentHandler() + test.before_testSax() + ch = contentHandler + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + assertTrue( parser.contentHandler ) + --ch = nil + --parser = nil + test.after_testSax() +end +function test.notestSAX_Parse_StartDocument_TextIn() + test.before_testSax() + ch = contentHandler + ch.startDocument = function( this ) this.started = true; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertTrue( ch.started ) +end +function test.notestSAX_Parse_StartDocument_FileIn() + test.before_testSax() + ch = contentHandler + ch.startDocument = function( this ) this.started = true; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "../build.xml" ) + assertTrue( ch.started ) +end +function test.notestSAX_Parse_StartDocument_NotGiven_TextIn() + test.before_testSax() + ch = contentHandler + ch.startDocument = nil + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertIsNil( ch.started ) +end +function test.testSAX_Parse_EndDocument_TextIn() + test.before_testSax() + ch = contentHandler + ch.endDocument = function( this ) this.ended = true; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertTrue( ch.ended ) +end +function test.testSAX_Parse_StartElement_TextIn() + -- affirm that the startElement method is called + test.before_testSax() + ch = contentHandler + ch.startElement = function( this, tagIn, attribs ) this.tagIn = tagIn; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertEquals( "xml", ch.tagIn ) +end +function test.testSAX_Parse_StartElementAttribs_TextIn() + -- affirm that the startElement method is called + test.before_testSax() + ch = contentHandler + ch.startElement = function( this, tagIn, attribs ) this.version = attribs["version"]; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertEquals( "1", ch.version ) +end +function test.testSAX_Parse_StartElementAttribs_TextIn2() + -- affirm that the startElement method is called + test.before_testSax() + ch = contentHandler + ch.startElement = function( this, tagIn, attribs ) this.version = attribs["version"]; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertEquals( "2", ch.version ) +end +function test.testSax_Parse_StartElement_Prolog() + test.before_testSax() + ch = contentHandler + ch.startElement = function( this, tagIn, attribs ) this.version = attribs["version"]; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertEquals( "3", ch.version ) +end +function test.testSax_Parse_StartElement_Comment() + test.before_testSax() + ch = contentHandler + ch.startElement = function( this, tagIn, attribs ) this.version = attribs["version"]; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertEquals( "4", ch.version ) +end +function test.testSax_Parse_NestedElements() + test.before_testSax() + ch = contentHandler + ch.startElement = function( this, tagIn, attribs ) this.broken = attribs["broken"]; end + parser = saxParser.makeParser() + parser.setContentHandler( ch ) + parser.parse( "" ) + assertEquals( "5", ch.broken ) +end ---------------------------------- -- Run the tests ----------------------------------