Index: trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestEvaluatePage.java
===================================================================
--- trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestEvaluatePage.java	(revision 44420)
+++ trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestEvaluatePage.java	(revision 44421)
@@ -1,94 +0,0 @@
-package org.gvsig.expresionevaluator.impl;
-
-import java.io.File;
-import junit.framework.TestCase;
-import org.gvsig.expressionevaluator.SymbolTable;
-import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
-import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
-import org.gvsig.expressionevaluator.ExpressionUtils;
-import org.gvsig.expressionevaluator.MutableSymbolTable;
-import org.gvsig.tools.ToolsLocator;
-import org.gvsig.tools.library.impl.DefaultLibrariesInitializer;
-
-public class TestEvaluatePage extends TestCase {
-    
-    public TestEvaluatePage(String testName) {
-        super(testName);
-    }
-    
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        new DefaultLibrariesInitializer().fullInitialize();
-    }
-    
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    // TODO add test methods here. The name must begin with 'test'. For example:
-    // public void testHello() {}
-    
-    protected SymbolTable createSymbolTable() {
-        ExpressionEvaluatorManager manager = ExpressionEvaluatorLocator.getManager();
-        MutableSymbolTable symbolTable = manager.createSymbolTable();
-        symbolTable.setVar("precio", 200);
-        symbolTable.setVar("PI", Math.PI);
-        return symbolTable;
-    }
-    
-    public void testSimple1() {
-        
-        SymbolTable symbolTable = createSymbolTable();
-        String s = ExpressionUtils.evaluateDynamicText(symbolTable, "el precion es <%=precio%>");
-
-        assertEquals("el precion es 200", s);
-    }
-    
-    public void testSimple2() {
-        String s = ExpressionUtils.evaluateDynamicText("el home es <%=HOME()%>");
-
-        assertEquals("el home es "+ToolsLocator.getFoldersManager().getHome().toString(), s);
-    }
-
-    public void testSimple3() {
-        String s = ExpressionUtils.evaluateDynamicText("un fichero temporal <%=UNIQUETEMPORARYFILE('nombre.txt')%>");
-        
-        // El resultado viene a ser algo como:
-        //   "un fichero temporal /tmp/tmp-gvsig/nombre-1-16d2f3e4f44.txt"
-        if( !s.startsWith("un fichero temporal ") || !s.contains("nombre-") || !s.endsWith(".txt") ) {
-            assertTrue("Expected 'un fichero temporal *nombre-*.txt' and found '"+s+"'", false);
-        }
-    }
-
-    public void test1() {
-        
-        SymbolTable symbolTable = createSymbolTable();
-        String s = ExpressionUtils.evaluateDynamicText(symbolTable, "el precion es <%=precio%> y PI*100 es <%=TOINTEGER(PI*100)%>");
-
-        assertEquals("el precion es 200 y PI*100 es 314", s);
-    }
-    
-    public void testFilename1() {
-        File source = new File("<%=UNIQUETEMPORARYFILE('nombre.txt')%>");
-        File target = ExpressionUtils.evaluateFilename(source);
-        
-        // El resultado viene a ser algo como:
-        //   /tmp/tmp-gvsig/nombre-1-16d2f3e4f44.txt
-        String s = target.getAbsolutePath();
-        if( !s.contains("nombre-") || !s.endsWith(".txt") ) {
-            assertTrue("Expected '*nombre-*.txt' and found '"+s+"'", false);
-        }
-    }
-
-    public void testFilename2() {
-        File source = new File("/tmp/prueba.txt");
-        File target = ExpressionUtils.evaluateFilename(source);
-        
-        String s = target.getAbsolutePath();
-        assertEquals("/tmp/prueba.txt", s);
-    }
-
-
-}
\ No newline at end of file
Index: trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestEvaluateDynamicText.java
===================================================================
--- trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestEvaluateDynamicText.java	(revision 0)
+++ trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestEvaluateDynamicText.java	(revision 44421)
@@ -0,0 +1,94 @@
+package org.gvsig.expresionevaluator.impl;
+
+import java.io.File;
+import junit.framework.TestCase;
+import org.gvsig.expressionevaluator.SymbolTable;
+import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
+import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
+import org.gvsig.expressionevaluator.ExpressionUtils;
+import org.gvsig.expressionevaluator.MutableSymbolTable;
+import org.gvsig.tools.ToolsLocator;
+import org.gvsig.tools.library.impl.DefaultLibrariesInitializer;
+
+public class TestEvaluateDynamicText extends TestCase {
+    
+    public TestEvaluateDynamicText(String testName) {
+        super(testName);
+    }
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        new DefaultLibrariesInitializer().fullInitialize();
+    }
+    
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    // TODO add test methods here. The name must begin with 'test'. For example:
+    // public void testHello() {}
+    
+    protected SymbolTable createSymbolTable() {
+        ExpressionEvaluatorManager manager = ExpressionEvaluatorLocator.getManager();
+        MutableSymbolTable symbolTable = manager.createSymbolTable();
+        symbolTable.setVar("precio", 200);
+        symbolTable.setVar("PI", Math.PI);
+        return symbolTable;
+    }
+    
+    public void testSimple1() {
+        
+        SymbolTable symbolTable = createSymbolTable();
+        String s = ExpressionUtils.evaluateDynamicText(symbolTable, "el precion es <%=precio%>");
+
+        assertEquals("el precion es 200", s);
+    }
+    
+    public void testSimple2() {
+        String s = ExpressionUtils.evaluateDynamicText("el home es <%=HOME()%>");
+
+        assertEquals("el home es "+ToolsLocator.getFoldersManager().getHome().toString(), s);
+    }
+
+    public void testSimple3() {
+        String s = ExpressionUtils.evaluateDynamicText("un fichero temporal <%=UNIQUETEMPORARYFILE('nombre.txt')%>");
+        
+        // El resultado viene a ser algo como:
+        //   "un fichero temporal /tmp/tmp-gvsig/nombre-1-16d2f3e4f44.txt"
+        if( !s.startsWith("un fichero temporal ") || !s.contains("nombre-") || !s.endsWith(".txt") ) {
+            assertTrue("Expected 'un fichero temporal *nombre-*.txt' and found '"+s+"'", false);
+        }
+    }
+
+    public void test1() {
+        
+        SymbolTable symbolTable = createSymbolTable();
+        String s = ExpressionUtils.evaluateDynamicText(symbolTable, "el precion es <%=precio%> y PI*100 es <%=TOINTEGER(PI*100)%>");
+
+        assertEquals("el precion es 200 y PI*100 es 314", s);
+    }
+    
+    public void testFilename1() {
+        File source = new File("<%=UNIQUETEMPORARYFILE('nombre.txt')%>");
+        File target = ExpressionUtils.evaluateFilename(source);
+        
+        // El resultado viene a ser algo como:
+        //   /tmp/tmp-gvsig/nombre-1-16d2f3e4f44.txt
+        String s = target.getAbsolutePath();
+        if( !s.contains("nombre-") || !s.endsWith(".txt") ) {
+            assertTrue("Expected '*nombre-*.txt' and found '"+s+"'", false);
+        }
+    }
+
+    public void testFilename2() {
+        File source = new File("/tmp/prueba.txt");
+        File target = ExpressionUtils.evaluateFilename(source);
+        
+        String s = target.getAbsolutePath();
+        assertEquals("/tmp/prueba.txt", s);
+    }
+
+
+}
\ No newline at end of file
Index: trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestInterpreter.java
===================================================================
--- trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestInterpreter.java	(revision 44420)
+++ trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/test/java/org/gvsig/expresionevaluator/impl/TestInterpreter.java	(revision 44421)
@@ -85,6 +85,18 @@
         assertEquals(23, ((Number)v).intValue());
     }
     
+    public void testNumberDMS() {
+        SymbolTable symbolTable = createSymbolTable();
+        Compiler compiler = createCompiler();
+        Interpreter interpreter = createInterpreter(symbolTable);
+        
+        String source = "@10 30 1.2 N";
+
+        Code code = compiler.compileExpression(source);
+        Object v = interpreter.run(code);
+        assertEquals(-10.5003, Math.round(((Number)v).doubleValue()*10000d)/10000d);
+    }
+    
     public void testTrue() {
         SymbolTable symbolTable = createSymbolTable();
         Compiler compiler = createCompiler();
Index: trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/main/java/org/gvsig/expressionevaluator/impl/SQLLexicalAnalyzer.java
===================================================================
--- trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/main/java/org/gvsig/expressionevaluator/impl/SQLLexicalAnalyzer.java	(revision 44420)
+++ trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.impl/src/main/java/org/gvsig/expressionevaluator/impl/SQLLexicalAnalyzer.java	(revision 44421)
@@ -211,7 +211,13 @@
         case '\'':
             parseString();
             return token;
+        
+        case '@':
+            ungetch();
+            parseSpecialNumber();
+            return token;
         }
+        
         if( Character.isDigit(ch) ) {
             ungetch();
             parseNumber();
Index: trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.api/src/main/java/org/gvsig/expressionevaluator/spi/AbstractLexicalAnalyzer.java
===================================================================
--- trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.api/src/main/java/org/gvsig/expressionevaluator/spi/AbstractLexicalAnalyzer.java	(revision 44420)
+++ trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.api/src/main/java/org/gvsig/expressionevaluator/spi/AbstractLexicalAnalyzer.java	(revision 44421)
@@ -313,6 +313,194 @@
         token.set(Token.STRING_LITERAL, buffer.toString());
     }
 
+    protected void parseSpecialNumber() {
+        char ch = getch();
+        if( ch!='@' ) {
+            throw new ExpressionSyntaxException(I18N.Wrong_special_number_start(), this);
+        }
+        buffer.clear();
+        ch = getch();
+        while (true) {
+            if (ch == EOF) {
+                throw new ExpressionSyntaxException(I18N.End_of_string_was_expected_and_end_of_source_was_found(), this);
+            }
+            if( !Character.isAlphabetic(ch) ) {
+                break;
+            }
+            buffer.add(ch);
+            ch = getch();
+        }
+        String formatName = buffer.toString();
+        if( StringUtils.isBlank(formatName) ) {
+            formatName = "DMS";
+        }
+        formatName = formatName.toUpperCase();
+        /*
+        From https://es.wikipedia.org/wiki/Sistema_de_coordenadas#Coordenadas_geogr%C3%A1ficas
+        
+        DD --- Decimal Degree (Grados Polares)
+        DM --- Degree:Minute (Grados:Minutos)
+        DMS -- Degree:Minute:Second 
+        */
+        switch(formatName) {
+            case "DMS":
+                ungetch();
+                parseDMSNumber();
+                break;
+            default:
+                throw new ExpressionSyntaxException(I18N.Special_number_format_not_supported(formatName), this);
+        }
+    }
+
+    private void parseDMSNumber() {
+        int d;
+        int m;
+        int s_i;
+        int s_d;
+        double s;
+        String ll;
+        char ch;
+        
+        // Parseamos los grados
+        skipblanks();
+        ch = getch();
+        buffer.clear();
+        if( !Character.isDigit(ch) ) {
+            throw new ExpressionSyntaxException(I18N.Expected_a_number_at_position_XpositionX(this.getPosition()), this);        
+        }
+        while (true) {
+            if (ch == EOF) {
+                throw new ExpressionSyntaxException(I18N.End_of_string_was_expected_and_end_of_source_was_found(), this);        
+            }
+            if( !Character.isDigit(ch) ) {
+                break;
+            }
+            buffer.add(ch);
+            ch = getch();
+        }
+        if( !StringUtils.contains(" º:", ch) ) {
+            throw new ExpressionSyntaxException(I18N.Expected_XexpectedX_and_found_XfoundX(" ", String.valueOf(ch)), this);
+        }
+        d = Integer.parseInt(buffer.toString());
+        
+        // Parseamos los minutos
+        skipblanks();
+        ch = getch();
+        buffer.clear();
+        if( !Character.isDigit(ch) ) {
+            throw new ExpressionSyntaxException(I18N.Expected_a_number_at_position_XpositionX(this.getPosition()), this);        
+        }
+        while (true) {
+            if (ch == EOF) {
+                throw new ExpressionSyntaxException(I18N.End_of_string_was_expected_and_end_of_source_was_found(), this);        
+            }
+            if( !Character.isDigit(ch) ) {
+                break;
+            }
+            buffer.add(ch);
+            ch = getch();
+        }
+        if( !StringUtils.contains(" ':", ch) ) {
+            throw new ExpressionSyntaxException(I18N.Expected_XexpectedX_and_found_XfoundX(" ", String.valueOf(ch)), this);
+        }
+        m = Integer.parseInt(buffer.toString());
+
+        // Parseamos la parte entera de los segundos
+        skipblanks();
+        ch = getch();
+        buffer.clear();
+        if( !Character.isDigit(ch) ) {
+            throw new ExpressionSyntaxException(I18N.Expected_a_number_at_position_XpositionX(this.getPosition()), this);        
+        }
+        while (true) {
+            if (ch == EOF) {
+                throw new ExpressionSyntaxException(I18N.End_of_string_was_expected_and_end_of_source_was_found(), this);        
+            }
+            if( !Character.isDigit(ch) ) {
+                break;
+            }
+            buffer.add(ch);
+            ch = getch();
+        }
+        s_i = Integer.parseInt(buffer.toString());
+
+        if( ch == '.' ) {
+            // Parseamos la parte decimal de los segundos
+            skipblanks();
+            ch = getch();
+            buffer.clear();
+            if( !Character.isDigit(ch) ) {
+                throw new ExpressionSyntaxException(I18N.Expected_a_number_at_position_XpositionX(this.getPosition()), this);        
+            }
+            while (true) {
+                if (ch == EOF) {
+                    throw new ExpressionSyntaxException(I18N.End_of_string_was_expected_and_end_of_source_was_found(), this);        
+                }
+                if( !Character.isDigit(ch) ) {
+                    break;
+                }
+                buffer.add(ch);
+                ch = getch();
+            }            
+            s_d = Integer.parseInt(buffer.toString());
+        } else {
+            s_d = 0;
+        }
+        if( !StringUtils.contains(" \"", ch) ) {
+            throw new ExpressionSyntaxException(I18N.Expected_XexpectedX_and_found_XfoundX(" ", String.valueOf(ch)), this);
+        }
+        
+        s = Double.parseDouble(s_i+"."+s_d);
+        
+        double dd = d + m / 60.0 + s / 3600.0;
+        
+        skipblanks();
+        ch = getch();
+        switch(ch) {
+            case 'N':
+            case 'n':
+                if( dd>90 ) {
+                    throw new ExpressionSyntaxException(I18N.Incorrect_value_for_latitude(dd), this);
+                }
+                dd = -dd;
+                break;
+                
+            case 'S':
+            case 's':
+                if( dd>90 ) {
+                    throw new ExpressionSyntaxException(I18N.Incorrect_value_for_latitude(dd), this);
+                }
+                break;
+            case 'E':
+            case 'e':
+                if( dd>180 ) {
+                    throw new ExpressionSyntaxException(I18N.Incorrect_value_for_latitude(dd), this);
+                }
+                break;
+                
+            case 'O':
+            case 'o':
+            case 'W':
+            case 'w':
+                if( dd>180 ) {
+                    throw new ExpressionSyntaxException(I18N.Incorrect_value_for_latitude(dd), this);
+                }
+                dd = -dd;
+                break;
+                
+            default:
+                throw new ExpressionSyntaxException(I18N.Expected_XexpectedX_and_found_XfoundX("N/S/E/W", String.valueOf(ch)), this);
+        }
+        ll = String.valueOf(Character.toUpperCase(ch));
+        
+        token.set(
+                Token.FLOATING_POINT_LITERAL,
+                String.format("@%dº %d' %f\" %s", d,m,s,ll) ,
+                dd
+        );
+        
+    }
+    
     protected void parseNumber() {
         this.nfPos.setIndex(this.position);
         Number n = nf.parse(source, this.nfPos);
Index: trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.api/src/main/java/org/gvsig/expressionevaluator/I18N.java
===================================================================
--- trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.api/src/main/java/org/gvsig/expressionevaluator/I18N.java	(revision 44420)
+++ trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.expressionevaluator/org.gvsig.expressionevaluator.lib/org.gvsig.expressionevaluator.lib.api/src/main/java/org/gvsig/expressionevaluator/I18N.java	(revision 44421)
@@ -37,6 +37,22 @@
     public static String Expected_a_number_at_position_XpositionX(int position) {
         return String.format("Expected a number at position %d.", position);
     }
+
+    public static String Wrong_special_number_start() {
+        return "wrong special number start";
+    }
+
+    public static String Special_number_format_not_supported(String formatName) {
+        return String.format("special number format '%s' not supported", formatName);
+    }
+
+    public static String Incorrect_value_for_latitude(double dd) {
+        return String.format("Incorrect value for latitude (%f)", dd);
+    }
+
+    public static String Incorrect_value_for_longitude(double dd) {
+        return String.format("Incorrect value for longitude (%f)", dd);
+    }
     
     
 }
