<reponame>Grosskopf/openoffice<filename>main/toolkit/src2xml/source/srcparser.py
# *************************************************************
#  
#  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.
#  
# *************************************************************

import sys
from globals import *
import srclexer

# simple name translation map
postTransMap = {"ok-button": "okbutton",
                "cancel-button": "cancelbutton",
                "help-button": "helpbutton"}

def transName (name):
    """Translate a mixed-casing name to dash-separated name.

Translate a mixed-casing name (e.g. MyLongName) to a dash-separated name
(e.g. my-long-name).
"""
    def isUpper (c):
        return c >= 'A' and c <= 'Z'

    newname = ''
    parts = []
    buf = ''
    for c in name:
        if isUpper(c) and len(buf) > 1:
            parts.append(buf)
            buf = c
        else:
            buf += c

    if len(buf) > 0:
        parts.append(buf)

    first = True
    for part in parts:
        if first:
            first = False
        else:
            newname += '-'
        newname += part.lower()

    # special-case mapping ...
    if 0: #postTransMap.has_key(newname):
        newname = postTransMap[newname]

    return newname


def transValue (value):
    """Translate certain values.

Examples of translated values include TRUE -> true, FALSE -> false.
"""
    if value.lower() in ["true", "false"]:
        value = value.lower()
    return value


def renameAttribute (name, elemName):

    # TODO: all manner of evil special cases ...
    if elemName == 'metric-field' and name == 'spin-size':
        return 'step-size'

    return name


class Statement(object):
    """Container to hold information for a single statement.

Each statement consists of the left-hand-side token(s), and right-hand-side
tokens, separated by a '=' token.  This class stores the information on the
left-hand-side tokens.
"""
    def __init__ (self):
        self.leftTokens = []
        self.leftScope = None


class MacroExpander(object):
    def __init__ (self, tokens, defines):
        self.tokens = tokens
        self.defines = defines

    def expand (self):
        self.pos = 0
        while self.pos < len(self.tokens):
            self.expandToken()

    def expandToken (self):
        token = self.tokens[self.pos]
        if token not in self.defines:
            self.pos += 1
            return

        macro = self.defines[token]
        nvars = len(list(macro.vars.keys()))
        if nvars == 0:
            # Simple expansion
            self.tokens[self.pos:self.pos+1] = macro.tokens
            return
        else:
            # Expansion with arguments.
            values, lastPos = self.parseValues()
            newtokens = []
            for mtoken in macro.tokens:
                if mtoken in macro.vars:
                    # variable
                    pos = macro.vars[mtoken]
                    valtokens = values[pos]
                    for valtoken in valtokens:
                        newtokens.append(valtoken)
                else:
                    # not a variable
                    newtokens.append(mtoken)

            self.tokens[self.pos:self.pos+lastPos+1] = newtokens


    def parseValues (self):
        """Parse tokens to get macro function variable values.

Be aware that there is an implicit quotes around the text between the open
paren, the comma(s), and the close paren.  For instance, if a macro is defined
as FOO(a, b) and is used as FOO(one two three, and four), then the 'a' must be
replaced with 'one two three', and the 'b' replaced with 'and four'.  In other
words, whitespace does not end a token.

"""
        values = []
        i = 1
        scope = 0
        value = []
        while True:
            try:
                tk = self.tokens[self.pos+i]
            except IndexError:
                progress ("error parsing values (%d)\n"%i)
                for j in range(0, i):
                    print(self.tokens[self.pos+j], end=' ')
                print('')
                srclexer.dumpTokens(self.tokens)
                srclexer.dumpTokens(self.newtokens)
                print("tokens expanded so far:")
                for tk in self.expandedTokens:
                    print("-"*20)
                    print(tk)
                    srclexer.dumpTokens(self.defines[tk].tokens)
                sys.exit(1)
            if tk == '(':
                value = []
                scope += 1
            elif tk == ',':
                values.append(value)
                value = []
            elif tk == ')':
                scope -= 1
                values.append(value)
                value = []
                if scope == 0:
                    break
                else:
                    raise ParseError ('')
            else:
                value.append(tk)
            i += 1

        return values, i

    def getTokens (self):
        return self.tokens


class SrcParser(object):

    def __init__ (self, tokens, defines = None):
        self.tokens = tokens
        self.defines = defines
        self.debug = False
        self.onlyExpandMacros = False

    def init (self):
        self.elementStack = [RootNode()]
        self.stmtData = Statement()
        self.tokenBuf = []
        self.leftTokens = []

        # Expand defined macros.
        if self.debug:
            progress ("-"*68+"\n")
            for key in list(self.defines.keys()):
                progress ("define: %s\n"%key)

        self.expandMacro()
        self.tokenSize = len(self.tokens)

    def expandMacro (self):
        macroExp = MacroExpander(self.tokens, self.defines)
        macroExp.expand()
        self.tokens = macroExp.getTokens()
        if self.onlyExpandMacros:
            srclexer.dumpTokens(self.tokens)
            sys.exit(0)

    def parse (self):
        """Parse it!

This is the main loop for the parser.  This is where it all begins and ends.
"""
        self.init()

        i = 0
        while i < self.tokenSize:
            tk = self.tokens[i]
            if tk == '{':
                i = self.openBrace(i)
            elif tk == '}':
                i = self.closeBrace(i)
            elif tk == ';':
                i = self.semiColon(i)
            elif tk == '=':
                i = self.assignment(i)
            else:
                self.tokenBuf.append(tk)

            i += 1

        return self.elementStack[0]

    #-------------------------------------------------------------------------
    # Token Handlers

    """
Each token handler takes the current token position and returns the position
of the last token processed.  For the most part, the current token position
and the last processed token are one and the same, in which case the handler
can simply return the position value it receives without incrementing it.

If you need to read ahead to process more tokens than just the current token,
make sure that the new token position points to the last token that has been
processed, not the next token that has not yet been processed.  This is
because the main loop increments the token position when it returns from the
handler.
"""

    # assignment token '='
    def assignment (self, i):
        self.leftTokens = self.tokenBuf[:]
        if self.stmtData.leftScope == None:
            # Keep track of lhs data in case of compound statement.
            self.stmtData.leftTokens = self.tokenBuf[:]
            self.stmtData.leftScope = len(self.elementStack) - 1

        self.tokenBuf = []
        return i

    # open brace token '{'
    def openBrace (self, i):
        bufSize = len(self.tokenBuf)
        leftSize = len(self.leftTokens)
        obj = None
        if bufSize == 0 and leftSize > 0:
            # Name = { ...
            obj = Element(self.leftTokens[0])

        elif bufSize > 0 and leftSize == 0:
            # Type Name { ...
            wgtType = self.tokenBuf[0]
            wgtRID = None
            if bufSize >= 2:
                wgtRID = self.tokenBuf[1]
            obj = Element(wgtType, wgtRID)

        else:
            # LeftName = Name { ...
            obj = Element(self.leftTokens[0])
            obj.setAttr("type", self.tokenBuf[0])

        obj.name = transName(obj.name)

        if obj.name == 'string-list':
            i = self.parseStringList(i)
        elif obj.name == 'filter-list':
            i = self.parseFilterList(i, obj)
        else:
            self.elementStack[-1].appendChild(obj)
            self.elementStack.append(obj)

        self.tokenBuf = []
        self.leftTokens = []

        return i

    # close brace token '}'
    def closeBrace (self, i):
        if len(self.tokenBuf) > 0:
            if self.debug:
                print(self.tokenBuf)
            raise ParseError ('')
        self.elementStack.pop()
        return i

    # semi colon token ';'
    def semiColon (self, i):
        stackSize = len(self.elementStack)
        scope = stackSize - 1
        if len(self.tokenBuf) == 0:
            pass
        elif scope == 0:
            # We are not supposed to have any statement in global scope.
            # Just ignore this statement.
            pass
        else:
            # Statement within a scope.  Import it as an attribute for the
            # current element.
            elem = self.elementStack[-1]

            name = "none"
            if len(self.leftTokens) > 0:
                # Use the leftmost token as the name for now.  If we need to
                # do more complex parsing of lhs, add more code here.
                name = self.leftTokens[0]
                name = transName(name)

            if name == 'pos':
                i = self.parsePosAttr(i)
            elif name == 'size':
                i = self.parseSizeAttr(i)
            elif len (self.tokenBuf) == 1:
                # Simple value
                value = transValue(self.tokenBuf[0])
                name = renameAttribute(name, elem.name)
                elem.setAttr(name, value)

            if not self.stmtData.leftScope == None and self.stmtData.leftScope < scope:
                # This is a nested scope within a statement.  Do nothing for now.
                pass

        if self.stmtData.leftScope == scope:
            # end of (nested) statement.
            self.stmtData.leftScope = None

        self.tokenBuf = []
        self.leftTokens = []

        return i

    def parseStringList (self, i):

        i += 1
        while i < self.tokenSize:
            tk = self.tokens[i]
            if tk == '}':
                break
            i += 1

        return i

    def parseFilterList (self, i, obj):
        self.elementStack[-1].appendChild(obj)
        self.elementStack.append(obj)

        return i

    def parsePosAttr (self, i):

        # MAP_APPFONT ( 6 , 5 )
        elem = self.elementStack[-1]
        x, y = self.parseMapAppfont(self.tokenBuf)
        elem.setAttr("x", x)
        elem.setAttr("y", y)

        return i

    def parseSizeAttr (self, i):

        # MAP_APPFONT ( 6 , 5 )
        elem = self.elementStack[-1]
        width, height = self.parseMapAppfont(self.tokenBuf)
        elem.setAttr("width", width)
        elem.setAttr("height", height)

        return i

    def parseMapAppfont (self, tokens):
        values = []
        scope = 0
        val = ''
        for tk in tokens:
            if tk == '(':
                scope += 1
                if scope == 1:
                    val = ''
                else:
                    val += tk
            elif tk == ')':
                scope -= 1
                if scope == 0:
                    if len(val) == 0:
                        raise ParseError ('')
                    values.append(val)
                    break
                else:
                    val += tk
            elif tk == ',':
                if len(val) == 0:
                    raise ParseError ('')
                values.append(val)
                val = ''
            elif scope > 0:
                val += tk

        if len(values) != 2:
            raise ParseError ('')

        return eval(values[0]), eval(values[1])

# *************************************************************
#  
#  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.
#  
# *************************************************************

import sys
from globals import *
import srclexer

# simple name translation map
postTransMap = {"ok-button": "okbutton",
                "cancel-button": "cancelbutton",
                "help-button": "helpbutton"}

def transName (name):
    """Translate a mixed-casing name to dash-separated name.

Translate a mixed-casing name (e.g. MyLongName) to a dash-separated name
(e.g. my-long-name).
"""
    def isUpper (c):
        return c >= 'A' and c <= 'Z'

    newname = ''
    parts = []
    buf = ''
    for c in name:
        if isUpper(c) and len(buf) > 1:
            parts.append(buf)
            buf = c
        else:
            buf += c

    if len(buf) > 0:
        parts.append(buf)

    first = True
    for part in parts:
        if first:
            first = False
        else:
            newname += '-'
        newname += part.lower()

    # special-case mapping ...
    if 0: #postTransMap.has_key(newname):
        newname = postTransMap[newname]

    return newname


def transValue (value):
    """Translate certain values.

Examples of translated values include TRUE -> true, FALSE -> false.
"""
    if value.lower() in ["true", "false"]:
        value = value.lower()
    return value


def renameAttribute (name, elemName):

    # TODO: all manner of evil special cases ...
    if elemName == 'metric-field' and name == 'spin-size':
        return 'step-size'

    return name


class Statement(object):
    """Container to hold information for a single statement.

Each statement consists of the left-hand-side token(s), and right-hand-side
tokens, separated by a '=' token.  This class stores the information on the
left-hand-side tokens.
"""
    def __init__ (self):
        self.leftTokens = []
        self.leftScope = None


class MacroExpander(object):
    def __init__ (self, tokens, defines):
        self.tokens = tokens
        self.defines = defines

    def expand (self):
        self.pos = 0
        while self.pos < len(self.tokens):
            self.expandToken()

    def expandToken (self):
        token = self.tokens[self.pos]
        if token not in self.defines:
            self.pos += 1
            return

        macro = self.defines[token]
        nvars = len(list(macro.vars.keys()))
        if nvars == 0:
            # Simple expansion
            self.tokens[self.pos:self.pos+1] = macro.tokens
            return
        else:
            # Expansion with arguments.
            values, lastPos = self.parseValues()
            newtokens = []
            for mtoken in macro.tokens:
                if mtoken in macro.vars:
                    # variable
                    pos = macro.vars[mtoken]
                    valtokens = values[pos]
                    for valtoken in valtokens:
                        newtokens.append(valtoken)
                else:
                    # not a variable
                    newtokens.append(mtoken)

            self.tokens[self.pos:self.pos+lastPos+1] = newtokens


    def parseValues (self):
        """Parse tokens to get macro function variable values.

Be aware that there is an implicit quotes around the text between the open
paren, the comma(s), and the close paren.  For instance, if a macro is defined
as FOO(a, b) and is used as FOO(one two three, and four), then the 'a' must be
replaced with 'one two three', and the 'b' replaced with 'and four'.  In other
words, whitespace does not end a token.

"""
        values = []
        i = 1
        scope = 0
        value = []
        while True:
            try:
                tk = self.tokens[self.pos+i]
            except IndexError:
                progress ("error parsing values (%d)\n"%i)
                for j in range(0, i):
                    print(self.tokens[self.pos+j], end=' ')
                print('')
                srclexer.dumpTokens(self.tokens)
                srclexer.dumpTokens(self.newtokens)
                print("tokens expanded so far:")
                for tk in self.expandedTokens:
                    print("-"*20)
                    print(tk)
                    srclexer.dumpTokens(self.defines[tk].tokens)
                sys.exit(1)
            if tk == '(':
                value = []
                scope += 1
            elif tk == ',':
                values.append(value)
                value = []
            elif tk == ')':
                scope -= 1
                values.append(value)
                value = []
                if scope == 0:
                    break
                else:
                    raise ParseError ('')
            else:
                value.append(tk)
            i += 1

        return values, i

    def getTokens (self):
        return self.tokens


class SrcParser(object):

    def __init__ (self, tokens, defines = None):
        self.tokens = tokens
        self.defines = defines
        self.debug = False
        self.onlyExpandMacros = False

    def init (self):
        self.elementStack = [RootNode()]
        self.stmtData = Statement()
        self.tokenBuf = []
        self.leftTokens = []

        # Expand defined macros.
        if self.debug:
            progress ("-"*68+"\n")
            for key in list(self.defines.keys()):
                progress ("define: %s\n"%key)

        self.expandMacro()
        self.tokenSize = len(self.tokens)

    def expandMacro (self):
        macroExp = MacroExpander(self.tokens, self.defines)
        macroExp.expand()
        self.tokens = macroExp.getTokens()
        if self.onlyExpandMacros:
            srclexer.dumpTokens(self.tokens)
            sys.exit(0)

    def parse (self):
        """Parse it!

This is the main loop for the parser.  This is where it all begins and ends.
"""
        self.init()

        i = 0
        while i < self.tokenSize:
            tk = self.tokens[i]
            if tk == '{':
                i = self.openBrace(i)
            elif tk == '}':
                i = self.closeBrace(i)
            elif tk == ';':
                i = self.semiColon(i)
            elif tk == '=':
                i = self.assignment(i)
            else:
                self.tokenBuf.append(tk)

            i += 1

        return self.elementStack[0]

    #-------------------------------------------------------------------------
    # Token Handlers

    """
Each token handler takes the current token position and returns the position
of the last token processed.  For the most part, the current token position
and the last processed token are one and the same, in which case the handler
can simply return the position value it receives without incrementing it.

If you need to read ahead to process more tokens than just the current token,
make sure that the new token position points to the last token that has been
processed, not the next token that has not yet been processed.  This is
because the main loop increments the token position when it returns from the
handler.
"""

    # assignment token '='
    def assignment (self, i):
        self.leftTokens = self.tokenBuf[:]
        if self.stmtData.leftScope == None:
            # Keep track of lhs data in case of compound statement.
            self.stmtData.leftTokens = self.tokenBuf[:]
            self.stmtData.leftScope = len(self.elementStack) - 1

        self.tokenBuf = []
        return i

    # open brace token '{'
    def openBrace (self, i):
        bufSize = len(self.tokenBuf)
        leftSize = len(self.leftTokens)
        obj = None
        if bufSize == 0 and leftSize > 0:
            # Name = { ...
            obj = Element(self.leftTokens[0])

        elif bufSize > 0 and leftSize == 0:
            # Type Name { ...
            wgtType = self.tokenBuf[0]
            wgtRID = None
            if bufSize >= 2:
                wgtRID = self.tokenBuf[1]
            obj = Element(wgtType, wgtRID)

        else:
            # LeftName = Name { ...
            obj = Element(self.leftTokens[0])
            obj.setAttr("type", self.tokenBuf[0])

        obj.name = transName(obj.name)

        if obj.name == 'string-list':
            i = self.parseStringList(i)
        elif obj.name == 'filter-list':
            i = self.parseFilterList(i, obj)
        else:
            self.elementStack[-1].appendChild(obj)
            self.elementStack.append(obj)

        self.tokenBuf = []
        self.leftTokens = []

        return i

    # close brace token '}'
    def closeBrace (self, i):
        if len(self.tokenBuf) > 0:
            if self.debug:
                print(self.tokenBuf)
            raise ParseError ('')
        self.elementStack.pop()
        return i

    # semi colon token ';'
    def semiColon (self, i):
        stackSize = len(self.elementStack)
        scope = stackSize - 1
        if len(self.tokenBuf) == 0:
            pass
        elif scope == 0:
            # We are not supposed to have any statement in global scope.
            # Just ignore this statement.
            pass
        else:
            # Statement within a scope.  Import it as an attribute for the
            # current element.
            elem = self.elementStack[-1]

            name = "none"
            if len(self.leftTokens) > 0:
                # Use the leftmost token as the name for now.  If we need to
                # do more complex parsing of lhs, add more code here.
                name = self.leftTokens[0]
                name = transName(name)

            if name == 'pos':
                i = self.parsePosAttr(i)
            elif name == 'size':
                i = self.parseSizeAttr(i)
            elif len (self.tokenBuf) == 1:
                # Simple value
                value = transValue(self.tokenBuf[0])
                name = renameAttribute(name, elem.name)
                elem.setAttr(name, value)

            if not self.stmtData.leftScope == None and self.stmtData.leftScope < scope:
                # This is a nested scope within a statement.  Do nothing for now.
                pass

        if self.stmtData.leftScope == scope:
            # end of (nested) statement.
            self.stmtData.leftScope = None

        self.tokenBuf = []
        self.leftTokens = []

        return i

    def parseStringList (self, i):

        i += 1
        while i < self.tokenSize:
            tk = self.tokens[i]
            if tk == '}':
                break
            i += 1

        return i

    def parseFilterList (self, i, obj):
        self.elementStack[-1].appendChild(obj)
        self.elementStack.append(obj)

        return i

    def parsePosAttr (self, i):

        # MAP_APPFONT ( 6 , 5 )
        elem = self.elementStack[-1]
        x, y = self.parseMapAppfont(self.tokenBuf)
        elem.setAttr("x", x)
        elem.setAttr("y", y)

        return i

    def parseSizeAttr (self, i):

        # MAP_APPFONT ( 6 , 5 )
        elem = self.elementStack[-1]
        width, height = self.parseMapAppfont(self.tokenBuf)
        elem.setAttr("width", width)
        elem.setAttr("height", height)

        return i

    def parseMapAppfont (self, tokens):
        values = []
        scope = 0
        val = ''
        for tk in tokens:
            if tk == '(':
                scope += 1
                if scope == 1:
                    val = ''
                else:
                    val += tk
            elif tk == ')':
                scope -= 1
                if scope == 0:
                    if len(val) == 0:
                        raise ParseError ('')
                    values.append(val)
                    break
                else:
                    val += tk
            elif tk == ',':
                if len(val) == 0:
                    raise ParseError ('')
                values.append(val)
                val = ''
            elif scope > 0:
                val += tk

        if len(values) != 2:
            raise ParseError ('')

        return eval(values[0]), eval(values[1])
# ************************************************************* # #  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. # # ************************************************************* # simple name translation map Translate a mixed-casing name to dash-separated name.  Translate a mixed-casing name (e.g. MyLongName) to a dash-separated name (e.g. my-long-name). # special-case mapping ... #postTransMap.has_key(newname): Translate certain values.  Examples of translated values include TRUE -> true, FALSE -> false. # TODO: all manner of evil special cases ... Container to hold information for a single statement.  Each statement consists of the left-hand-side token(s), and right-hand-side tokens, separated by a '=' token.  This class stores the information on the left-hand-side tokens. # Simple expansion # Expansion with arguments. # variable # not a variable Parse tokens to get macro function variable values.  Be aware that there is an implicit quotes around the text between the open paren, the comma(s), and the close paren.  For instance, if a macro is defined as FOO(a, b) and is used as FOO(one two three, and four), then the 'a' must be replaced with 'one two three', and the 'b' replaced with 'and four'.  In other words, whitespace does not end a token. # Expand defined macros. Parse it!  This is the main loop for the parser.  This is where it all begins and ends. #------------------------------------------------------------------------- # Token Handlers Each token handler takes the current token position and returns the position of the last token processed.  For the most part, the current token position and the last processed token are one and the same, in which case the handler can simply return the position value it receives without incrementing it.  If you need to read ahead to process more tokens than just the current token, make sure that the new token position points to the last token that has been processed, not the next token that has not yet been processed.  This is because the main loop increments the token position when it returns from the handler. # assignment token '=' # Keep track of lhs data in case of compound statement. # open brace token '{' # Name = { ... # Type Name { ... # LeftName = Name { ... # close brace token '}' # semi colon token ';' # We are not supposed to have any statement in global scope. # Just ignore this statement. # Statement within a scope.  Import it as an attribute for the # current element. # Use the leftmost token as the name for now.  If we need to # do more complex parsing of lhs, add more code here. # Simple value # This is a nested scope within a statement.  Do nothing for now. # end of (nested) statement. # MAP_APPFONT ( 6 , 5 ) # MAP_APPFONT ( 6 , 5 )