2012-10-17 24 views
6

Sto lavorando alla creazione di una mappa di riferimento formula dal foglio di calcolo xml usando python. la formula è comeparsing formula excel style

=IF(AND(LEN(R[-2]C[-1])>0,R[-1]C),WriteCurve(OFFSET(R16C6, 0,0,R9C7,R10C7),R15C6,R10C3, R8C3),"NONE") 

Mi interessa solo ottenere l'ennesimo argomento della funzione writecurve. qui mi viene in mente un programma in stile C che conta fondamentalmente coma che non è all'interno della parentesi. ci sono un sacco di formula nidificata

def parseArguments(t, func, n): 
start=t.find(func)+len(func)+1 
bracket = 0 
ss = t[start:] 
lastcomma = 0 
for i, a in enumerate(ss): 
    if a=="(": 
     bracket +=1 
    elif a==")": 
     if bracket==0: 
      break 
     bracket-=1 
    elif a == ",": 
     if bracket==0 and n==0: 
      break 
     elif bracket ==0: 
      if n-1==0: 
       lastcomma = i 
      n-=1 
if lastcomma == 0: 
    return ss[:i] 
else: 
    return ss[lastcomma+1:i] 

C'è un modo pitonico per farlo? o c'è un modo ricorsivo migliore per analizzare l'intera formula? Molte grazie

risposta

8

Il miglior parser di Excel di cui sono a conoscenza è E. W. Bachtal's algorithm. C'è una porta Python di Robin Macharg; la versione più recente che conosco è parte dello pycel project, ma può essere utilizzata autonomamente - tokenizer. Non ha problemi durante l'analisi della formula:

from tokenizer import shunting_yard 
rpn = shunting_yard('=IF(AND(LEN(R[-2]C[-1])>0,R[-1]C),WriteCurve(OFFSET(R16C6, 0,0,R9C7,R10C7),R15C6,R10C3, R8C3),"NONE")') 
print(rpn) 
deque([<tokenizer.RangeNode object at 0x2b7b1f5d7850>, <tokenizer.FunctionNode object at 0x2b7b1f5d7950>, <tokenizer.ASTNode object at 0x2b7b1f5d7990>, <tokenizer.ASTNode object at 0x2b7b1f5d79d0>, <tokenizer.RangeNode object at 0x2b7b1f5d7a10>, <tokenizer.FunctionNode object at 0x2b7b1f5d7a50>, <tokenizer.RangeNode object at 0x2b7b1f5d7a90>, <tokenizer.ASTNode object at 0x2b7b1f5d7ad0>, <tokenizer.ASTNode object at 0x2b7b1f5d7b10>, <tokenizer.RangeNode object at 0x2b7b1f5d7b50>, <tokenizer.RangeNode object at 0x2b7b1f5d7b90>, <tokenizer.FunctionNode object at 0x2b7b1f5d7bd0>, <tokenizer.RangeNode object at 0x2b7b1f5d7c10>, <tokenizer.RangeNode object at 0x2b7b22efc450>, <tokenizer.RangeNode object at 0x2b7b22efc510>, <tokenizer.FunctionNode object at 0x2b7b22efc410>, <tokenizer.ASTNode object at 0x2b7b22eff110>, <tokenizer.FunctionNode object at 0x2b7b22eff150>]) 

Il tokenizzatore ti lascia uno stack RPN; se ci si trova a lavorare con un AST più conveniente è possibile convertire facilmente ad un AST:

def rpn_to_ast(rpn): 
    stack = [] 
    for n in rpn: 
     num_args = (2 if n.token.ttype == "operator-infix" else 
        1 if n.token.ttype.startswith('operator') else 
        n.num_args if n.token.ttype == 'function' else 0) 
     n.args = [stack.pop() for _ in range(num_args)][::-1] 
     stack.append(n) 
    return stack[0] 

È possibile poi a piedi l'AST per trovare il nodo WriteCurve ed esaminare i suoi argomenti:

def walk(ast): 
    yield ast 
    for arg in getattr(ast, 'args', []): 
     for node in walk(arg): 
      yield node 

write_curve = next(node for node in walk(rpn_to_ast(rpn)) if node.token.ttype == 'function' and node.token.tvalue == 'WriteCurve') 
print(write_curve.args[2].token.tvalue) 
R10C3 
Problemi correlati