Child pages
  • Cím szétbontása utca, házszám stb. elemekre
Skip to end of metadata
Go to start of metadata

Hello,

még mindig számlázó progit írok. A probléma a következő. Adott az adatbázisban egy mező, ami szövegesen tárolja a delikvens címét (magyarországi), mégpedig az utcanévtől kezdve. Pl. "Alfa krt. 9 2em 1", "Béta u. 12/A" rendkívül sok formában. Ezt kellene szétbontani egy algoritmussal (utcanév, típus, szám, épület, lépcsőház, emelet, ajtó) formára, ahol az első 3 rész kötelezően létezik, a maradék opcionális.

Valakinek van valami megoldása már erre?

Köszönettel: karnokd

      
      
Page viewed times
#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))
  • No labels

20 Comments

  1. Miért akarod szétbontani a címet? Van olyan eset, amikor nem egyben akarod használni?
  2. A számlázó programnak elő kell állítania egy már létező xml formátumában is a számla adatokat, és ott ilyen felbontásban kell megadni. Ez az xml megy majd egy másik programnak. Sem az adatforrás, sem a felhasználás fölött nincs befolyásom.
  3. Unknown User (frimen)

    Rendesen belefutottál valami hig-trágya-fosba nem mondom... ekkora agymenést rég hallottam..:)
    Nincs olyan algoritmus ami ezt Neked hibátlanul megcsinálja..
    Én a helyedben regex-el csinálnám meg, aminek "parametereit" (egy ablakban modosithatová (bővithetővé)
    tenném, mert hogy lesznek kivételek az tuti, meg lesz olyan amit nem lehet sehogysem kielemezni.. ez is biztos.
    Xar ügy.

  4. Azt hiszem sikerült kitalálni egy heurisztikát. Ha valakit érdekel:

    public class Cim {
      /**
       * Ország.
       */
      public String orszag = "Magyarország";
      /**
       * Település.
       */
      public String telepules;
      /**
       * Irányítószám.
       */
      public int irszam;
      /**
       * A közterület neve.
       */
      public String kozterNev;
      /**
       * A közterület jellege (utca, tér, stb.).
       */
      public String kozterJelleg;
      /**
       * Házszám.
       */
      public int hazszam;
      /**
       * Opcionális épület rész.
       */
      public String epulet;
      /**
       * Opcionális lépcsőház.
       */
      public String lepcsohaz;
      /**
       * Opcionális emelet.
       */
      public String emelet;
      /**
       * Opcionális ajtó.
       */
      public String ajto;
      /**
       * Cím szétbontása.
       * @param cim a nyers szöveg.
       * @return true ha sikerült
       */
      public boolean parseCim(String cim) {
        Cim ovr = CF.get(telepules + "|" + cim);
        if (ovr != null) {
          this.kozterNev = ovr.kozterNev;
          this.kozterJelleg = ovr.kozterJelleg;
          this.hazszam = ovr.hazszam;
          this.epulet = ovr.epulet;
          this.lepcsohaz = ovr.lepcsohaz;
          this.emelet = ovr.emelet;
          this.ajto = ovr.ajto;
          return true;
        }
        List<String> elemek = normalizalas(cim);
        // köztérnév kiderítése
        int i = 0;
        while (i < elemek.size() && isTulajdonnev(i, elemek)) {
          i++;
        }
        // legalább három szóból kell állnia
        if (elemek.size() < 3) {
          return false;
        }
        if (i >= elemek.size() - 1) {
          return false;
        }
        StringBuilder b = new StringBuilder();
        for (int j = 0; j < i; j++) {
          if (j > 0) {
            b.append(' ');
          }
          b.append(elemek.get(j));
        }
        kozterNev = b.toString().trim();
       
        // köztér jelleg
        kozterJelleg = elemek.get(i);
       
        // házszám
        String hazszamStr = elemek.get(i + 1);
        int posthaz = i + 2;
        if (isSzamtartomany(hazszamStr)) {
          int idx = hazszamStr.indexOf('-');
          hazszam = szamnev(hazszamStr.substring(0, idx));
        } else {
          if (!isSzamnev(hazszamStr)) {
            return false;
          }
          hazszam = szamnev(hazszamStr);
        }
       
        // részcím heurisztikák
        if (posthaz < elemek.size()) {
          // ajtó szám
         
          int last = elemek.size() - 1;
         
          // néhány cím a végén postafiókot tartalmaz
          if (!"Pf.".equals(elemek.get(last - 1))
              || !isSzamnev(elemek.get(last))) {
         
            if ("ajtó".equals(elemek.get(last))) {
              ajto = elemek.get(last - 1);
              // előtte perjellel emelet
              if ("/".equals(elemek.get(last - 2))) {
                if (isSzamnev(elemek.get(last - 3))) {
                  emelet = elemek.get(last - 3);
                }
              }
            }
            if (posthaz < last - 2 && "/".equals(elemek.get(posthaz))) {
              lepcsohaz = elemek.get(posthaz + 1);
            }
            // ha a hazszam utan / van utána egy érték és vége, akkor az érték
            // megy, akkor az a lépcsőház lesz
            if (posthaz == last - 1 && "/".equals(elemek.get(last - 1))) {
              lepcsohaz = elemek.get(last);
            } else {
              if (isSzamnev(elemek.get(last))) {
                ajto = elemek.get(last);
                String last1 = elemek.get(last - 1);
                if ("/".equals(last1)) {
                  String last2 = elemek.get(last - 2);
                  if (isSzamnev(last2)) {
                    emelet = last2;
                  } else
                  if (isRomaiszam(last2)) {
                    emelet = last2;
                  } else
                  if (last - 2 == posthaz) {
                    emelet = last2;
                  }
                } else
                if ("fszt".equalsIgnoreCase(last1)
                  || "fsz".equalsIgnoreCase(last1)
                  || "fszt.".equalsIgnoreCase(last1)
                  || "fsz.".equalsIgnoreCase(last1)) {
                  emelet = last1;
                }
              }
            }
           
            // emelet
            int idx = elemek.indexOf("em");
           
            if (idx < 0) {
              idx = elemek.indexOf("emelet");
            }
            if (idx < 0) {
              idx = elemek.indexOf("em.");
            }
            if (idx > 0) {
              emelet = elemek.get(idx - 1);
            }
           
            // épület
            idx = elemek.indexOf("ép");
            if (idx < 0) {
              idx = elemek.indexOf("ép.");
            }
            if (idx < 0) {
              idx = elemek.indexOf("épület");
            }
            if (idx > 0) {
              epulet = elemek.get(idx - 1);
            }
          }     
        }
        return true;
      }
      /**
       * Római szám?
       * @param s szöveg
       * @return true ha római szám.
       */
      private static boolean isRomaiszam(String s) {
        for (int i = 0; i < s.length(); i++) {
          switch (s.charAt(i)) {
          case 'I':
          case 'V':
          case 'X':
          case 'L':
          case 'C':
          case 'M':
          case 'D':
            break;
          default:
            return false;
          }
        }
        return true;
      }
      /**
       * Szöveg normalizálása és szavakra bontása.
       * @param s a szöveg
       * @return a szavak
       */
      private static List<String> normalizalas(String s) {
        List<String> result = new LinkedList<String>();
        StringBuilder sb = new StringBuilder();
        char last = '\0';
        for (int i = 0; i < s.length(); i++) {
          char c = s.charAt(i);
          if (c != ' ') {
            if (c == '.') {
              sb.append(c);
              c = ' ';
            } else
            if (c == '/') {
              if (last != ' ') {
                sb.append(' ');
              }
              sb.append(c);
              c = ' ';
            }
            sb.append(c);
          } else
          if (c == ' ' && last != ' ') {
            sb.append(' ');
          }
          last = c;
        }
        s = sb.toString().trim();

        if (s.length() > 0) {
          int start = 0;
          int end = s.indexOf(' ');
          while (end >= 0) {
            String e = s.substring(start, end);
            if (!".".equals(e)) {
              result.add(e);
            }
            start = end + 1;
            end = s.indexOf(' ', start);
          }
          if (end < 0) {
            String e = s.substring(start);
            if (!".".equals(e)) {
              result.add(e);
            }
          }
        }
        return result;
      }
      /**
       * A megadott szöveg számtartomány?
       * @param s a szöveg
       * @return true ha számtartomány
       */
      public static boolean isSzamtartomany(String s) {
        int idx = s.indexOf('-');
        if (idx > 0) {
          if (!isSzamnev(s.substring(0, idx))) {
            return false;
          }
          if (!isSzamnev(s.substring(idx + 1))) {
            return false;
          }
          return true;
        }
        return false;
      }
      /**
       * A megadott szöveg nagybetűvel keződik?
       * @param i az index
       * @param elemek az elemek lista
       * @return true ha nagybetűvel kezdődik
       */
      public boolean isTulajdonnev(int i, List<String> elemek) {
        String s = elemek.get(i);
        if (s.length() > 0) {
          if (Character.isUpperCase(s.charAt(0))) {
            // ha a körtér nagybetűvel van írva
            if ("Krt.".equals(s)) {
              return false;
            } else
            if ("Ligetsor".equals(s) && "Baracs".equals(telepules)) {
              elemek.set(i, "Liget");
              elemek.add(i + 1, "sor");
            }
            return true;
          } else {
            // Mátyás király kivétel
            if ("király".equals(s) && i > 0 && "Mátyás".equals(elemek.get(i - 1))) {
              return true;
            }
            // Március 15. kivétel
            if ("15.".equals(s) && i > 0 && "Március".equals(elemek.get(i - 1))) {
              return true;
            }
          }
        }
        return false;
      }
      /**
       * A szöveg számnév?
       * @param s a szöveg
       * @return true ha számnév
       */
      public static boolean isSzamnev(String s) {
        int len = s.length();
        if (s.charAt(len - 1) == '.') {
          len--;
        }
        for (int i = 0; i < len; i++) {
          if (!Character.isDigit(s.charAt(i))) {
            return false;
          }
        }
        return true;
      }
      /**
       * Számnév értékének lekérdezése.
       * @param s szöveg
       * @return szám
       */
      public static int szamnev(String s) {
        int len = s.length();
        if (s.charAt(len - 1) == '.') {
          len--;
        }
        return Integer.parseInt(s.substring(0, len));
      }
      /**
       * Teszt program.
       * @param args argumentumok
       * @throws Exception figyelmen kívül hagyva
       */
      public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new FileReader("Cimek.txt"));
        String line = null;
        while ((line = in.readLine()) != null) {
          int idx = line.indexOf('|');
          Cim c = new Cim();
          c.telepules = line.substring(0, idx);
          if (c.parseCim(line.substring(idx + 1))) {
            System.out.print(line);
            int j = line.length();
            while (j++ < 40) {
              System.out.print(' ');
            }
            System.out.print(" -> "
                + c.kozterNev + " | "
                + c.kozterJelleg + " | "
                + c.hazszam + " | ");
            if (c.epulet != null) {
              System.out.print("Épület: " + nvl(c.epulet) + " | ");
            }
            if (c.lepcsohaz != null) {
              System.out.print("Lépcsőház: " + nvl(c.lepcsohaz) + " | ");
            }
            if (c.emelet != null) {
              System.out.print("Emelet: " + nvl(c.emelet) + " | ");
            }
            if (c.ajto != null) {
              System.out.print("Ajtó : " + nvl(c.ajto));
            }
            System.out.println();
          } else {
            System.out.println("Sikertelen: " + line);
          }
        }
        in.close();
      }
      /**
       * Null helyett üres szöveget ad vissza.
       * @param s szöveg
       * @return szöveg vagy üres
       */
      private static String nvl(String s) {
        return s != null ? s : "";
      }
      /**
       * Új Cim objektum létrehozása és feltöltése a komponensekből.
       * @param utca az utca neve
       * @param kozterJelleg a köztér jellege
       * @param hazszam a házszám
       * @param epulet az épület
       * @param lepcsohaz a lépcsőház
       * @param emelet az emelet
       * @param ajto az ajtó
       * @return az Cim objektum
       */
      private static Cim newCim(
          String utca, String kozterJelleg, int hazszam,
          String epulet, String lepcsohaz, String emelet, String ajto) {
        Cim r = new Cim();
        r.kozterNev = utca;
        r.kozterJelleg = kozterJelleg;
        r.hazszam = hazszam;
        r.epulet = epulet;
        r.lepcsohaz = lepcsohaz;
        r.emelet = emelet;
        r.ajto = ajto;
        return r;
      }
      /**
       * A normál Cim objektum konvertálása az  cím formátumú objektumává.
       * @param from a forrás Cim objektum
       * @param to a cél  cim objektum
       */
      public static void convert(Cim from, Cim to) {
        to.telepules = from.helyiseg;
        to.irszam = Integer.parseInt(from.irsz);
        to.parseCim(from.utcaHazszam);
      }
      /**
       * Cím felülbíráló map. Kulcs formátum település|nyerscím
       */
      private static final Map<String, Cim> CF;
      /**
       * Statikus inicializáló.
       */
      static {
        CF = new ConcurrentHashMap<String, Cim>();
        // ---------------------------------------------------------------
        CF.put("Táborfalva|Honvéd u. 10/11. A ép. fsz.1.", newCim(
            "Honvéd", "u.", 10, "A", null, "fsz.", "1"));
       
        CF.put("Veszprém|Március 15. u. 1/C. 6 em. 22/a.", newCim(
            "Március 15.", "u.", 1, null, "C.", "6", "22/a"
            ));
      }
    }

  5. Hát ez gyönyörű.
    És az összes királyt bele fogod drótozni vagy csak Mátyásnak jár ez a kiváltság?
  6. Végülis, véges számú királyunk volt.
  7. bocs, ha nagyon triviális dolgot kérdezek (úgy tanul a gyerek, ha kérdez :D)...

    public String orszag = "Magyarország";

    miért public? az attribútumok általában private-ok szoktak lenni, nem?
  8. Unknown User (frimen)

    Jobban járt volna a világ, ha hentesnek tanulsz!:-)
    Csak még fél kilómérnyi sor hiányzik belőle, amire később fogsz ráeszmélni nagy valószínűséggel.:)

    Én azon csodálkozom, hogy miért nem final, vagy static Magyarország.
    Tudsz valamit? Jön még egy trianon vagy ismét lesz a sogorokkal monarhia?:))
  9. Kivételesen Frimen-nel értek egyet, tényleg talán regexp-pel lehetne ezt jól megcsinálni.
    Nem vagyok egy regexp guru, de itt egy kis kód, amivel lehet próbálkozni.

    Három szövegmező, a felsőbe lehet írni a regexp-et, a középsőbe a szöveget, az alsóban pedig az eredményt írja ki. Megpróbálja csoportokra szedni a szöveget, ha sikerül neki illeszteni.

    import java.awt.BorderLayout;
    import java.awt.event.KeyAdapter;
    import java.awt.event.KeyEvent;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.util.regex.PatternSyntaxException;

    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JSplitPane;
    import javax.swing.JTextArea;


    public class Test {
       
        public static void main(String[] args) throws Exception {
            JFrame f = new JFrame("Test");
            f.setLayout(new BorderLayout());
            JSplitPane s1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
            JSplitPane s2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
            final JTextArea regex = new JTextArea();
            final JTextArea input = new JTextArea();
            final JTextArea result = new JTextArea();
            regex.addKeyListener(new KeyAdapter() {
                @Override
                public void keyReleased(KeyEvent e) {
                    result.setText(evaluate(regex.getText(), input.getText()));
                }
            });
            input.addKeyListener(new KeyAdapter() {
                @Override
                public void keyReleased(KeyEvent e) {
                    result.setText(evaluate(regex.getText(), input.getText()));
                }
            });
            s1.add(new JScrollPane(regex));
            s1.add(s2);
            s2.add(new JScrollPane(input));
            s2.add(new JScrollPane(result));
            result.setEditable(false);
            f.add(s1, BorderLayout.CENTER);
            f.setSize(400, 300);
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setVisible(true);
        }
       
        private static String evaluate(String regex, String input) {
            try {
                Pattern p = Pattern.compile(regex);
                Matcher m = p.matcher(input);
                boolean r = m.matches();
                StringBuilder result = new StringBuilder();
                if(r) {result.append("[Match]\n");
                    for(int i=1;i<=m.groupCount();i++) {
                        result.append(m.group(i));
                        result.append('\n');
                    }
                }
                return result.toString();
            } catch(PatternSyntaxException ex) {
                return ex.getDescription();
            }
        }

    }
  10. > És az összes királyt bele fogod drótozni vagy csak Mátyásnak jár ez a kiváltság?
    A teszt adatok között szerepelt egy ilyen utcanév. Kisbetűvel volt írva a király, ezért az algoritmus már nem sorolta a köztérnévhez. Ugyanez a helyzet a "Krt." szöveggel, ahol viszont a köztérjelleg volt nagybetűvel, ami miatt megint nem adott helyes eredményt.
    A teszt adathalmazt meg egy google kereséssel szereztem.

    > Jobban járt volna a világ, ha hentesnek tanulsz!:-)
    > Csak még fél kilómérnyi sor hiányzik belőle, amire később fogsz ráeszmélni nagy valószínűséggel.:)
    Mikor bepostoltam, akkor láttam igazán, hogy ilyen hosszú a kód. Hát ugyen nincs szerkesztés...

    > miért public? az attribútumok általában private-ok szoktak lenni, nem?
    Mivel én írom az osztályt, és azt a helyet, ahol felhasználom, ezért nem szivatom magam get()/set() metódusokkal. Továbbá nem library kód, és más rajtam kívül nem fog vele továbbfejleszteni, nem lesz többszálasan használva vagy beanként használva. Ezt úgy hívják "smart record". Mivel a java-ban nincsenek rekordok, ezért kénytelen vagyok osztályokat használni.

    > Én azon csodálkozom, hogy miért nem final, vagy static Magyarország.
    > Tudsz valamit? Jön még egy trianon vagy ismét lesz a sogorokkal monarhia?:))
    Az ország nem kerül tárolásra a forrás rendszerben, így explicite megmondták nekem. Az mindig Magyarország. Azért nem final meg static, mert egyszer talán továbbfejlesztik a forrás rendszert is...

    > Kivételesen Frimen-nel értek egyet, tényleg talán regexp-pel lehetne ezt jól megcsinálni.
    Nyilván lehet valami regexp-et rátenni, de úgy találtam, hogy többre jutok a heurisztikával, mint egy regexp-pel. Pont a szintaktikai problémák miatt. A legelején gondoltam arra, hogy a köztérjelleget úgy keresem meg, hogy egy előre gyártott listát (u., utca, út, sor, tér, stb) keresek a szövegben, és a találat után ami előtte van az az utcanév, ami utána az meg a házszám. Kiderült az abev2006-ból, hogy összesen 160 féle köztérjelleg ismert az adóhatóság előtt, és ezek még csak nem is rövidítések. El lehet képzelni hányféle név

  11. "Mivel én írom az osztályt, és azt a helyet, ahol felhasználom, ezért nem szivatom magam get()/set() metódusokkal. Továbbá nem library kód, és más rajtam kívül nem fog vele továbbfejleszteni, nem lesz többszálasan használva vagy beanként használva. Ezt úgy hívják "smart record". Mivel a java-ban nincsenek rekordok, ezért kénytelen vagyok osztályokat használni."

    Ezt hívják úgynevezett gányolásnak. Ha holnap a fejedre esik egy műhold, akkor más fogja továbbfejleszteni a kódot, és szidni fog téged is keményen, mivel nem úgy fog kinézni a kód, ahogy megszokta.
  12. Ha holnap fejemre esik a műhold, akkor senki nem fogja továbbfejleszteni. Jó eséllyel dobják a projektet - és nem a kódstílus miatt, hanem azon rengeteg tudás miatt, ami velem szállt a sírba. Egyébként, ha megvan a source kód és rendesen le van javadoc-olva, akkor nincs semmi gond. Én következetesen így programozok.

  13. A teszt adatok között szerepelt egy ilyen utcanév. Kisbetűvel volt írva a király, ezért az algoritmus már nem sorolta a köztérnévhez. Ugyanez a helyzet a "Krt." szöveggel, ahol viszont a köztérjelleg volt nagybetűvel, ami miatt megint nem adott helyes eredményt.

    Tesztadatokat a kódba égetni... hmm.

    A köztérjelleget és a közterület nevét úgy nyerném ki, hogy átugrom az első két szót, aztán megkeresem az első szót, ami számmal kezdődik. A talált szó a házszám, az azelőtti szó jó eséllyel a közterület jellege, az azelőtti szavak a pedig a közterület neve. (Szó: whitespace-ekkel vagy string elejével-végével határolt, whitespace-eket nem tartalmazó karaktersorozat.) Előfeltétel, hogy a házszám mindig megvan és mindig számmal kezdődik, előtte pedig a köztér jellege van feltüntetve. Kisbetű-nagybetű nem számít. Ezt még talán a regexp is megcsinálja.

    De ha nem regexp, csinálnék ilyeneket, hogy az eddig megtalált közterület jellegeket, közterület neveket eltárolgatnám és ráellenőriznék a kiadott eredményre. Valami tanuló algoritmus féleség.
  14. Én úgy gondolom, hogy ez egy olyan feladat, amit nem lehet szépen megcsinálni... :)

    Ha lenne idő rá, akkor minden beérkező szöveges címből csinálnék valamilyen jellemzőket (egyes részek hossza, jellege, karakterek, írásjelek, stb), és egy másfél órát rászánni, hogy egy adatbázist feltöltsünk meg tanított adatokkal, és később se lesz ebből soha hibamentes rendszer.

    Ki lehet próbálni, hogy kinek mit csinál a programja például a "Budapest, Október 23. utca 12/B" címmel... :)

    És akkor még nem beszéltünk az elgépelt vagy rosszul írt címekkel, amelyek szemmel olvasva nem tűnnek fel, mert az agyunk automatikusan korrigál, de a program nem fog... :)

    A beillesztett program pedig egy kezdetleges vázlat, amelyre már lehet építeni... legalábbis én így látom.
  15. Budapest, Október 23. utca 12/B

    A várost nem kell beleírni. Város nélkül az alább írt algoritmusom pl. ezt is megeszi. :)
  16. "Ha holnap fejemre esik a műhold, akkor senki nem fogja továbbfejleszteni. Jó eséllyel dobják a projektet - és nem a kódstílus miatt, hanem azon rengeteg tudás miatt, ami velem szállt a sírba. Egyébként, ha megvan a source kód és rendesen le van javadoc-olva, akkor nincs semmi gond. Én következetesen így programozok."

    Azt hiszem, hogy nagyon távol áll egymástól kettőnk programozási stílusa. Én erősen törekszem arra, hogy ne legyen olyan információ, ami csak nálam van meg. Kell, hogy le legyen írva a dolog, esetleg, hogy más is tudjon róla. Így egyszerűbb...
  17. Erről mindíg az jut eszembe, hogy volt olyan adathalmaz, amiben az Ady Endre utca 1200 féle variációban volt leírva.
    Ha a fejedre esik egy műhód (), akkor mit foglalkozol az utánad jövőkkel? Vagy a jövővel?

    A dokumentálást szerintem a tervben kell leírni, nem a kódban. A kód csak a fontosabb dolgokat tarttalmazza, pl. in/out paraméterek, rövid leírás, stb.
  18. Én is gondoltam arra, hogy az éles indulás előtt generálunk egy adatbázisnyi cím felbontást, meghogy majd valaki beleírogatja menet közben az új címeket. Sajnos senki nem akar ezzel foglalkozni, így marad a heurisztika. Valóban az Október 23. címet nem ismeri fel, csak miután bekerül a heurisztikába, mint speciális eset. A regexp akkor jöhetett volna szóba, ha lenne valami azonosítható szerkezet, ami minden cím mintára illeszkedik, de mivel össze vissza írják be...
  19. Unknown User (frimen)

    Nem akarlak megsérteni, de mindazt amit megoldottál regexel is meg
    lehet csinálni.. más kérdés, hogy érteni kell hozzá.. ami a részedről szerintem hiányzik.
    Persze ezzel sokan így vannak, mert elég bonyolult..
    A legnagyobb probléma ezzel az egésszel az, hogy rossz végén van megfogva a tehén..
    A kulcs modatot meg Te mondtad ki: "de mivel össze vissza írják be".
    Te meg folyamatosan szopni fogsz, ha ez igy is marad..:)
  20. Üdv!

    A munkáltatóm rendelkezik ilyen jellegű eszközzel. (Tudom, mivel részt vettem a feljesztésében.) Ha gondolod vedd fel a kapcsolatot a Scriptummal.