From 970358aa5cf1bcd3d196db287d18ef140cababf5 Mon Sep 17 00:00:00 2001 From: Alex Kondrashov Date: Wed, 10 Apr 2024 10:26:11 +0300 Subject: [PATCH 1/2] Add a test for invalid value labels --- .../edu/cornell/ncrn/ced2ar/ddigen/Main.java | 44 ++--- .../ddigen/GenerateDdi32StatisticsTest.java | 171 ++++++++++++++++++ target/Extract2DDI.jar | Bin 5478078 -> 5477693 bytes 3 files changed, 193 insertions(+), 22 deletions(-) create mode 100644 src/test/java/edu/cornell/ncrn/ced2ar/ddigen/GenerateDdi32StatisticsTest.java diff --git a/src/main/java/edu/cornell/ncrn/ced2ar/ddigen/Main.java b/src/main/java/edu/cornell/ncrn/ced2ar/ddigen/Main.java index 6d909f8..fc8cfad 100644 --- a/src/main/java/edu/cornell/ncrn/ced2ar/ddigen/Main.java +++ b/src/main/java/edu/cornell/ncrn/ced2ar/ddigen/Main.java @@ -31,7 +31,7 @@ public class Main { private static String FORMAT_OUTPUT_2_5 = "2.5"; private static String FORMAT_OUTPUT_3_2 = "3.2"; - private static String FORMAT_OUTPUT_3_3_FRAGMENT = "3.3Fragment"; +// private static String FORMAT_OUTPUT_3_3_FRAGMENT = "3.3Fragment"; public static void main(String args[]) throws Exception { Util util = new Util(); @@ -134,27 +134,27 @@ public static void main(String args[]) throws Exception { if (summaryStats) { FileUtil.createFile(ddi.getVariableCsv().getStatistics(), dataFile+".stats.csv"); } - } else if (formatOutput.equalsIgnoreCase(FORMAT_OUTPUT_3_3_FRAGMENT)) { - if (agency == null || agency.isEmpty()) { - logger.error("Agency is required..."); - System.exit(1); - return; - } - if (ddiLanguage == null || ddiLanguage.isEmpty()) { - logger.error("DDI language is required..."); - System.exit(1); - return; - } - GenerateDDI33 generateDDI = new GenerateDDI33(agency, ddiLanguage, excludeVariableToStatMap, stats); - ddi = generateDDI.generateDDI(dataFile, summaryStats, obsLimit); - - if (isFrequencyFileEnabled) { - FileUtil.createFile(ddi.getVariableCsv().getFrequencies(), dataFile+".freq.csv"); - } - - if (summaryStats) { - FileUtil.createFile(ddi.getVariableCsv().getStatistics(), dataFile+".stats.csv"); - } +// } else if (formatOutput.equalsIgnoreCase(FORMAT_OUTPUT_3_3_FRAGMENT)) { +// if (agency == null || agency.isEmpty()) { +// logger.error("Agency is required..."); +// System.exit(1); +// return; +// } +// if (ddiLanguage == null || ddiLanguage.isEmpty()) { +// logger.error("DDI language is required..."); +// System.exit(1); +// return; +// } +// GenerateDDI33 generateDDI = new GenerateDDI33(agency, ddiLanguage, excludeVariableToStatMap, stats); +// ddi = generateDDI.generateDDI(dataFile, summaryStats, obsLimit); +// +// if (isFrequencyFileEnabled) { +// FileUtil.createFile(ddi.getVariableCsv().getFrequencies(), dataFile+".freq.csv"); +// } +// +// if (summaryStats) { +// FileUtil.createFile(ddi.getVariableCsv().getStatistics(), dataFile+".stats.csv"); +// } } else { throw new IllegalArgumentException("Unknown formatOutput: " + formatOutput); } diff --git a/src/test/java/edu/cornell/ncrn/ced2ar/ddigen/GenerateDdi32StatisticsTest.java b/src/test/java/edu/cornell/ncrn/ced2ar/ddigen/GenerateDdi32StatisticsTest.java new file mode 100644 index 0000000..0a960c5 --- /dev/null +++ b/src/test/java/edu/cornell/ncrn/ced2ar/ddigen/GenerateDdi32StatisticsTest.java @@ -0,0 +1,171 @@ +package edu.cornell.ncrn.ced2ar.ddigen; + +import edu.cornell.ncrn.ced2ar.ddigen.fragment.AbstractFragmentInstanceGeneratorTest; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; + +public class GenerateDdi32StatisticsTest { + + private static final Logger logger = Logger.getLogger(GenerateDdi32StatisticsTest.class); + + private static final boolean IS_SUMMARY_STATISTICS_ENABLED = true; + private static final long RECORD_LIMIT = 100; + + @Test + public void testSpssStatistics() throws Exception { + // Arrange + File file = FileUtil.getFileFromResource(AbstractFragmentInstanceGeneratorTest.class, "test-file-data-types.sav"); + + GenerateDDI32 generateDDI32 = new GenerateDDI32("uk.closer", "en-GB", new HashMap<>(),"max,min,mean,valid,invalid,freq,stdev"); + + // Act + DDI ddi = generateDDI32.generateDDI(file.getPath(), IS_SUMMARY_STATISTICS_ENABLED, RECORD_LIMIT); + + // Assert + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + InputStream stream = new ByteArrayInputStream(ddi.getXml().getBytes(StandardCharsets.UTF_8)); + Document document = builder.parse(stream); + + // Ensure there is 14 statistics in both STATA and SPSS files + NodeList variableStatisticsNodeList = document.getElementsByTagName("pi:VariableStatistics"); + + Assert.assertEquals("Invalid ValidCases statistic value", "0", variableStatisticsNodeList.item(0).getChildNodes().item(5).getChildNodes().item(3).getTextContent()); + Assert.assertEquals("Invalid InvalidCases statistic value","1", variableStatisticsNodeList.item(0).getChildNodes().item(7).getChildNodes().item(3).getTextContent()); + + Assert.assertEquals("Invalid Maximum statistic count", 9, StringUtils.countMatches(ddi.getXml(), "Maximum")); + Assert.assertEquals("Invalid Minimum statistic count", 9, StringUtils.countMatches(ddi.getXml(), "Minimum")); + Assert.assertEquals("Invalid ValidCases statistic count", 14, StringUtils.countMatches(ddi.getXml(), "ValidCases")); + Assert.assertEquals("Invalid InvalidCases statistic", 14, StringUtils.countMatches(ddi.getXml(), "InvalidCases")); + Assert.assertEquals("Invalid StandardDeviation statistic", 9, StringUtils.countMatches(ddi.getXml(), "StandardDeviation")); + Assert.assertEquals("Invalid Mean statistic count", 9, StringUtils.countMatches(ddi.getXml(), "Mean")); + } + + @Test + public void testStataStatistics() throws Exception { + // Arrange + File file = FileUtil.getFileFromResource(AbstractFragmentInstanceGeneratorTest.class, "test-file-data-types.dta"); + + GenerateDDI32 generateDDI32 = new GenerateDDI32("uk.closer", "en-GB", new HashMap<>(),"max,min,mean,valid,invalid,freq,stdev"); + + // Act + DDI ddi = generateDDI32.generateDDI(file.getPath(), IS_SUMMARY_STATISTICS_ENABLED, RECORD_LIMIT); + + // Assert + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + InputStream stream = new ByteArrayInputStream(ddi.getXml().getBytes(StandardCharsets.UTF_8)); + Document document = builder.parse(stream); + + // Ensure there is 14 statistics in both STATA and SPSS files + NodeList variableStatisticsNodeList = document.getElementsByTagName("pi:VariableStatistics"); + + Assert.assertEquals("Invalid ValidCases statistic value", "3", variableStatisticsNodeList.item(0).getChildNodes().item(5).getChildNodes().item(3).getTextContent()); + Assert.assertEquals("Invalid InvalidCases statistic value", "97", variableStatisticsNodeList.item(0).getChildNodes().item(7).getChildNodes().item(3).getTextContent()); + + Assert.assertEquals("Invalid StandardDeviation statistic count", 9, StringUtils.countMatches(ddi.getXml(), "StandardDeviation")); + Assert.assertEquals("Invalid Maximum statistic count", 9, StringUtils.countMatches(ddi.getXml(), "Maximum")); + Assert.assertEquals("Invalid Minimum statistic count", 9, StringUtils.countMatches(ddi.getXml(), "Minimum")); + Assert.assertEquals("Invalid ValidCases statistic count", 14, StringUtils.countMatches(ddi.getXml(), "ValidCases")); + Assert.assertEquals("Invalid InvalidCases statistic count", 14, StringUtils.countMatches(ddi.getXml(), "InvalidCases")); + Assert.assertEquals("Invalid Mean statistic count", 9, StringUtils.countMatches(ddi.getXml(), "Mean")); + } + + @Test + public void testSpssValidCasesStatistic() throws Exception { + // Arrange + File file = FileUtil.getFileFromResource(AbstractFragmentInstanceGeneratorTest.class, "test-file-data-types.sav"); + + GenerateDDI32 generateDDI32 = new GenerateDDI32("uk.closer", "en-GB", new HashMap<>(),"valid"); + + // Act + DDI ddi = generateDDI32.generateDDI(file.getPath(), IS_SUMMARY_STATISTICS_ENABLED, RECORD_LIMIT); + + // Assert + Assert.assertEquals("Invalid Maximum statistic count", 0, StringUtils.countMatches(ddi.getXml(), "Maximum")); + Assert.assertEquals("Invalid Minimum statistic count", 0, StringUtils.countMatches(ddi.getXml(), "Minimum")); + Assert.assertEquals("Invalid ValidCases statistic count", 14, StringUtils.countMatches(ddi.getXml(), "ValidCases")); + Assert.assertEquals("Invalid InvalidCases statistic count", 0, StringUtils.countMatches(ddi.getXml(), "InvalidCases")); + Assert.assertEquals("Invalid StandardDeviation statistic count", 0, StringUtils.countMatches(ddi.getXml(), "StandardDeviation")); + Assert.assertEquals("Invalid Mean statistic count", 0, StringUtils.countMatches(ddi.getXml(), "Mean")); + } + + @Test + public void testStataValidCasesStatistic() throws Exception { + // Arrange + File file = FileUtil.getFileFromResource(AbstractFragmentInstanceGeneratorTest.class, "test-file-data-types.dta"); + + GenerateDDI32 generateDDI32 = new GenerateDDI32("uk.closer", "en-GB", new HashMap<>(),"valid"); + + // Act + DDI ddi = generateDDI32.generateDDI(file.getPath(), IS_SUMMARY_STATISTICS_ENABLED, RECORD_LIMIT); + + // Assert + Assert.assertEquals("Invalid StandardDeviation statistic count", 0, StringUtils.countMatches(ddi.getXml(), "StandardDeviation")); + Assert.assertEquals("Invalid Maximum statistic count", 0, StringUtils.countMatches(ddi.getXml(), "Maximum")); + Assert.assertEquals("Invalid Minimum statistic count", 0, StringUtils.countMatches(ddi.getXml(), "Minimum")); + Assert.assertEquals("Invalid ValidCases statistic count", 14, StringUtils.countMatches(ddi.getXml(), "ValidCases")); + Assert.assertEquals("Invalid InvalidCases statistic count", 0, StringUtils.countMatches(ddi.getXml(), "InvalidCases")); + Assert.assertEquals("Invalid Mean statistic count", 0, StringUtils.countMatches(ddi.getXml(), "Mean")); + } + + @Test + public void testSpssExcludeStatistic() throws Exception { + // Arrange + File file = FileUtil.getFileFromResource(AbstractFragmentInstanceGeneratorTest.class, "test-file-data-types.sav"); + + GenerateDDI32 generateDDI32 = new GenerateDDI32( + "uk.closer", + "en-GB", + new HashMap() {{ put("TestInteger", "max,min,mean,valid,invalid,freq,stdev:remove statistics"); }}, + "max,min,mean,valid,invalid,freq,stdev" + ); + + // Act + DDI ddi = generateDDI32.generateDDI(file.getPath(), IS_SUMMARY_STATISTICS_ENABLED, RECORD_LIMIT); + + // Assert + Assert.assertEquals("Invalid Maximum statistic count", 11, StringUtils.countMatches(ddi.getXml(), "Maximum")); + Assert.assertEquals("Invalid Minimum statistic count", 11, StringUtils.countMatches(ddi.getXml(), "Minimum")); + Assert.assertEquals("Invalid ValidCases statistic count", 13, StringUtils.countMatches(ddi.getXml(), "ValidCases")); + Assert.assertEquals("Invalid InvalidCases statistic count", 13, StringUtils.countMatches(ddi.getXml(), "InvalidCases")); + Assert.assertEquals("Invalid StandardDeviation statistic count", 11, StringUtils.countMatches(ddi.getXml(), "StandardDeviation")); + Assert.assertEquals("Invalid Mean statistic count", 11, StringUtils.countMatches(ddi.getXml(), "Mean")); + } + + @Test + public void testStataExcludeStatistic() throws Exception { + // Arrange + File file = FileUtil.getFileFromResource(AbstractFragmentInstanceGeneratorTest.class, "test-file-data-types.dta"); + + GenerateDDI32 generateDDI32 = new GenerateDDI32( + "uk.closer", + "en-GB", + new HashMap() {{ put("TestInteger", "max,min,mean,valid,invalid,freq,stdev:remove statistics"); }}, + "max,min,mean,valid,invalid,freq,stdev" + ); + + // Act + DDI ddi = generateDDI32.generateDDI(file.getPath(), IS_SUMMARY_STATISTICS_ENABLED, RECORD_LIMIT); + + // Assert + Assert.assertEquals("Invalid Maximum statistic count", 8, StringUtils.countMatches(ddi.getXml(), "Maximum")); + Assert.assertEquals("Invalid Minimum statistic count", 8, StringUtils.countMatches(ddi.getXml(), "Minimum")); + Assert.assertEquals("Invalid ValidCases statistic count", 13, StringUtils.countMatches(ddi.getXml(), "ValidCases")); + Assert.assertEquals("Invalid InvalidCases statistic count", 13, StringUtils.countMatches(ddi.getXml(), "InvalidCases")); + Assert.assertEquals("Invalid StandardDeviation statistic count", 8, StringUtils.countMatches(ddi.getXml(), "StandardDeviation")); + Assert.assertEquals("Invalid Mean statistic count is incorrect", 8, StringUtils.countMatches(ddi.getXml(), "Mean")); + } +} \ No newline at end of file diff --git a/target/Extract2DDI.jar b/target/Extract2DDI.jar index 4dcddd3c8c5c65a5cb65143d69bca6069cbe58ca..a8a230b344aba6962e92715af124cb474e992c1f 100644 GIT binary patch delta 16957 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 H)Rg!?@wzLF delta 17657 zcmZWw1y~f_*QcbrML_I$2?;3` zMev)Q_4R%Ee>|LL?%dyr8|R*T&+JUotG77KC2w&^bzr!7#8`xcgjlxu6{Nb@IJWs! zme?fHI1)gjiwtymDWizK+mg9~zKF}}pfA_t@t?+<<|k4rUwfjQhGUbT zn69h|UP9NDBi59$LBatE7bHB8&Vhsv5&=krAQ6E?3=#=Qq#%)jL=F-KNR%K^fkX`w z4M?;g(Sbw{5(7w#Ae{$^38V`kF@wYc5-Ug-L1F`m9V8BrI6=Au5*J99LE;AK3P?O4 z@q)w$(p8Z7K@tE-5F{axu7M;Bk_brGK@tT?3?y-oBtVh`NeU!skYqrT1xXGhd5{!9 zQUpl}B=lIQfTRkN8c6COX@GR2W=&Z$EE*iv8rKoc>-gwG{s+x=Ty#O|F8n+Wy6|2B zJ_Xg3sK7IzLJJil3M!Z+z^2ckdF)wX2&h1G8O9DlL~;mAsHR>Ueu#sklwUKB4X36B zW|-ltXo=MTSQMl_6c@n)F$(y@i7_=|a2%+R^I3BeD(I3ST=39RRe5kJNIla9gc(%u zV@1?M1@B7;TZqS~6Fv>q42UB*pn@|T;Rxv|*MrMIH5KE35~qd2t19Lx476 zDFVkxrAxs@p&^mk)ue*NrIX`5#ljh zf^*_fNdRHS*T-ru>ycw&MYCgJNhP)^Z~&JdAupf`OapG2eZBWP$aYG*XIOHJxly4P z8+%4FDV;4nUPVix+f#3=QzB#deboWAiSXSgG~Bbfv!qYkO_9^C#jfk?S7bRR{0oY2 z)+GB{oAtZZm(0CO&YV+)v7OvsNOXZ2mXo1WPFWU?Pj3WlHvyK|CUkRPA!&oBFEriS9&I+`DVI5hq>5 zFg%}%az9C*XB?{S(n)|--9s3yKvxt8NsUOTjUapKjn~g(Q#T%2rH2Lhek~~wijgzd z+AWPl*^{yxm5l@^<7KBs&tw{|vRU1qvw8Ltm&s6j`(dhVE2CQXqe)zAH^N(Vyerlv z?NY_|6?fvn3>$hxq99ONU93}SPv7!|B;CNbv_5$LqY7;+c_~R`s&3m1HOss2 zMAaPl=PpH<0`1td8YAWvlC^>_q$#{Fx~&FRBW2S#)uu_84QHRX>8z_oBlDP}5_!@x ziI8I7V%x~`YlhaX9WV4t7Cdq`YmO4dx5`7QE0?t^8gm5^D=@2iBivuHpK?qVU0;UQ5uR+Y!5$v5|Q|lv#(&eq(GMW9L$6R zCE_TefQ9?Wsn+_lK92-&Pm4_QVt)smMU=Vb9cHqN0Q`1=F1i7kV~22=G1< zg>&y+6AOGVDX??F?YrC9uBVa%F&S(KMcsNsret^E2#?@xSTv1 z(-Q;gz4tG&434U=zmiza(8G@FN=Gg$b>EbiL}Q3>~cq@B#yUzDn4W=G2eB1wTTgb(`NQuAAIG? zpM2phDgN)bl5{*~X3l3&XH z@!^hpj9lGUFY776-rsqY_~k;G6;Q8s z7dT!mu*%DZ{x&$0|0UlrUE$gurLJerM9MN=8>hcr{~#T|3d~ z$4kli$j3smg|)4ei8YV=0yj;WOHePA5{!<9?M~mVv@#NMB9l1~@%A$Xe)hd(SJLVpjW_pV72VKzT=7`?2NPFbv492Qqaan1jEDS-&voIaII4Hs z*1UKK8F=e8CBzOb42l7E(CZ@ z6@fZrh_zf#;~UzpNCWQQcb$^fMjc~56D9DpOSyh4M5J?Ls|V|3efp|eD7T!I`|0C# z1>Z+r0Xd>kO4TW{gB(->d`1}q*AV=i+`tYmH3GoNlis-w}t!Q0B% z(vQ6!+OE477fXg{ao(h{3XSedd{Q$0g_Y9X!?-i|Dow`wyolU4Lvx+eDITHyt_RAp zsc$dORNcFM=^Hsq{DgVUqtz9ZMbS7nQpD4@%r1V?DmJcB9@jdK-z+~g{L6L*RLC^E`{664v-t@3}q&qBbLn=C6Ki zO;tcj1?Xi`bHi3?N>jD+5Udett&Iu#vZnF;e3nDYEAqkC z>yqoIfbDp-mQJR7sgG9QPh_F`pIbjSQ(#DTHFj1py>tJ1A3;Kx)y!p}FOJ6W?V_}$ zK=w{|1F51vmP0Cc%l+M%{BS?k`q=NHG~Y@Zv*oQ?E?E3%vvOJJKIY;&E_&DLToxqR zgW^-zw7Hq;`mO1viXy3i$m6n(jCe9ljEdnm;(MFQHzo>J>tMOlCQJf8>V&=$Di<+7u zZ}_ZB>NC#F(p};HIax6B8p~_5NmT`w6~E*k#(hy`w_h6_*b~MS+lUlRJ&dWvPN(dk zzvRK?(tN(tz@OM`kLS)i`*N~j&gIk%#ub7W;bV%&lQ}S0B=LzO|jcYEH-;vof;Z9L7nJjW}M-w%jEcJ zvFW@@U)TcZ?~|1w<394{S)9&>4V8BAS~sS5-T!@fCAMn(%WPBBO-~~;Io|g9an9N} z%F;1D^<7Gjbvs)HL1nyK-l=Xzm!f#ya+zDkx@`l)7H9<@2f3L+>NCN%<(F2A>v`%DbBO2M^00 zsCw<~W3}l9MQm&~Tm7;yzSf$7V?DRoTS}n5drY^!Qq)Dd=I2*9A6Rwkp{CgK#X7^q zD{sx*5{jAmGN?kE-hHaP(4Ad16;z+nVY#vk?0k0jIx_S7>L>XtwBe^GxqDp^YIvz- zcKr9s?j7Qgle#Yq(F@8%=si^Moy2p3zO3CJX+>UBRApxr181h`c)RE+lu`4}J zNl)t^Iu<>9wKM!~{BD7Nm*;q(Jad6Y8*Mdmt1AHBF!|>7Z6WL@{VTqDs6%eTgEi5N z%M)IcY88|do3koR~9a#4+s-xBkBsAr_*-mKv{CPbK?OAvB&0`y6%{=0`Ljq-8Ylpo> zcD^4jVrySyJ!EcE&a(FJJP>02DihfwGra6s7?u(K_HK2&YD8@nsTD5Eb3E>EgibHx zp1iP&vA$~Xv}ni7-0)|>CwqkH`kiGk0EtWm(c8p)=2153p3}5oTC7KvR&>qhayIT; z(HM;CGN~ln3jp!fKFs%1MAsMbjhEY}9<)c5?26Qf?u5nprl!lo65@998q$Orl9bw( zlans73d(Aa%K8X06q{smugk5PJqXVtSjYP=Dfs#wYkl@G?-c&Qdj28St98X63qydpm3VmV^unzom)yj|F|iW{9T)-Oe-lw1lE06U!0b0m7ZwSnuex@K=TM=8 z3;q$xen*aAF;Jlt1+RcITjnQl7O3Vf3@!)NG-tpUp?o+e2ad^^zZAf~5~B0!H=HnC zi1{iH>>`vZUz~*ZKvMTK5L>w9=nPrgb*bqI9u}52QR0#u7og>eq_1fhhZW;ND5dN> zl^kp=EOXq%I3+GXJ^)EnGph}2#ilaN4>LAkFJbE=!@?qF1+#0QLL0^jG@2k-P(jS& z0l!g2$#!wvB?2UZa`BQ}Ka5S}!i>7fQ(Ui_$d!lT39B16SO;v4mrhP4QxSz{@UvgK z@Js7TY7=dMiChy7&;q>|^UyB$}_v)6pet^un<-{+4@ zzyFTqMjd}$Yazas&rH!8DvWCp?uF7Px%$2+J7UndeQ+zbtIt{n z3)l81mA=jkT79zgsXW_|ZG>#kZR-jR;S{FJq0z}qI}ST0)fur^N-xB8@^v1gq}sYw zjW=Afot+*zeeDr8bh+XT_O`^p2(ZCyusJ>MJ=Nt<;K{)^`P*xj4n`g)8=0 z2j1fA#8tATaJVdLhT9qoI9OEkmhwnfRh)OmdyN>9tqQ3o)wJY!k;b(XIi>!+OAXgJ z*S4&E9KTezmVRQdbJdORsCcV-2F0}MvBNte9a4*pz<)|-TK;sOL4b-;-TK@8gXZGU zQpF~8O09x7pQK!xy}Hfzv32B{Nalm@z-pX>Kh{6bSLw9_XNWJ{>8^Y}FO=iaFrO3S?O4L7 zFA*(G^pJz|r*&V3b7aZ3pxO--wo`1`j$tieEwu%IOQ`hv-B zG26u|;}epFqTK_g;AWTU z%)=$CEP-5G^7Qi;S#z~Tjf4D?@;7LH=d@d&yS2qQ{8TO|HSz8?Ei!#B{}>o<42l?$XA(c`Qp*N?XsTiy%Ee#3QA6?lETz z?bfynE&=C~tJc|sueeNZRR8cKk1$gdUi3pXT+nf#h-5BUTKVhY zx5jqKrl$7-Sz}nK`u?m)m7=u%_ePiF8tt^)*dx2v&fRQomLj;6A>?sitL>Abc?0a= z+V0mbUv+7k52RN@{izo8{coTciiu4tCN5>WzsvhkI!1z0UYfa^J1j|;rG{nM;pb!d z%`7C8E3X01Hlc0#QXl1g^(DW(KpzS&tznbr+gP_g;$U6Hq5OHPxoDT*=PjQ%0=Hik zo@y+`ebo-_1gtRc(g2H`ZH?hld;4O5ceTlA0keu)k{u%cXMOY_~> zUFtU7_hfNVg}dL6BI=qi)>N$$ZpW<&pU*28X1ey>r0&VSa5yDp`>kjh#)G{~sfk4v;@xm$*Ridb6M zyEu5nu%wn)GL~?t>Sc}k5#ElvIEQ-*w8!l&;CvGE4J4Mea31|V&Y>-?82G$l<#$^V z{{2_GPrr>l9hic{9%g?CL9iL#T3N@Y@S z^dM$FXz_U&eSH4G@GiVRS>M4gnR_soxx@D5QPL6D;yhhcrz5G4;JS^87fOZa<=CeQ zXS45{OqbZX#`QXsQV`PjUgx~r@F21Wa?ZD#smv`s*)T}C;uz1DYT=zv^r1bxFD7QS zAP>gAM zRF2j4YHjltLGjM}UNHs34Y3^5@v>{McBz$}Q}vL)RPqR8%4U+epm@1R-tR*8hUbh$ znSH6QA~5r-=Kj=~8+Z-bI&sOo*X{33>o$@cKKod)0*_mvZko!M5)v0&C4arRujcr* zV<$rLJ8?~eirqb2anZ~4tZ%%TOamCAUXz&6&>Qk*4RQ@qZ--LsQUiygPFY;_56{nY>1Ql6`|j zL^`nKbB`UF=2bgBlI)m@&` z->lA;(jDul4Hxydd6R@(s@v<6hO?_tB<~M?5P$c=C4cDV0dwtFG7g1<=?Vu62E_+< z??op1#hoqJE>x7&4*AsTzwbWseD#qzoIRqDb5K*go}5rX|B3Z93bu34^vTb> zqGN`envV{an+pVQz8Gm_zN+K@0QXIWO7~i)|H3=?&#=eTs4~%vC|mFIu`NF?yTRR= z34do3^ae7)K7R18+zlL2aF+ri)9a~)^yu~Td`;;;mhq{EAUR)e4rFR9bxGh+@QEC= z&)xCTf9om9%l5vrjIc!oX@+{MnDwcZb12AAqEpRr30X&C^Sbz5v`tCzN8Y9%*M#@? zn!Jd+S=JWgQX&S7)~FsCR)_JWl=bKHI)%m9+tq$(A6O&X%_kCd=oX$|eH|t%bR7u6 zla10S;`A*j2E=-TlPeX)@}dKQ-pf<)KG7FRcj^-A1(efV60VSLENY?zeP{dUUcd1E zdU%@Z?DVoTmS+q({LtaY=NskOQHF4FB)>- z5Q9Izoyb$|`L)|Yh?$gNe=@0u-p>cg>h7tYSy%yY@yk}ryziI!Q29$*@RBU9y9q17 zY;se;VMD(3ek~uf`SJiKYSksJm2T;lsQi#e`8NmFo_0mfII4(kycDm}>C6khtB0EQ z!XgjoPu{v3E-lh_zucU0TFB}d=GP5q6p8yTG4M5_s?i%~+-9GI{B@(kmS^Obbk5g) zIM~z+Ws$#r1vjiJzhx@3K(dl+EpxJxhLQDLLrSPF(IiUTeE2}5OdhrA{#`Iz;f1ZJ z5mV9IC8OyR{-ym-0Wwr%UhzF;YU8wTy@uEFl9&2DgyM!9NSY~2mi-J^EwkhrbS6_f zy+dvUg+~~$l-{8rnfcl)S%Dd`G?3j(TAv30RB0e=|-iv%|e!GrMkA2`2Gu z{K$2`h;+1}WtidQFk%VSkB?$SwF|yIRjt*tp4zQ7ay8&d3mp)Srkk4|>p=!QaVX=jm=g%~wLf1K;$z)v5MW+c#}3y}&WL`*gCxS45Ec+$RqsacuGAoIf z;w{Ye`dFo%t3(oTH%jM$af6ii60C^Z_T?gJk;-dnQll1)`{9EI+yklgFRYcrO$7PW z_Pq=;%@I!$d+$$D9$hVQYFGA8eIpaCo>6llUD>#Q?btsnE{FOHs_=DO9_z(hLHFaO z<-&ZAgz^tKqZ<>WE93TiwU&hB9!e~5!nwF5ep6=Mnw7gzs_oH}X#Rs%-F{bQL2%KL zn_eyQ7`wYYQ^lCh4}V4ArFGlQ;nb#d34`eY5rgetK6oC_-tBm*F@3astI-nzNNM-U zGH;0dY-~*Cyyz>xxrmZmyj7F1dGqBpP2ajXu@2YZ!eY;QJi4;{zRj=A_}P9_MGiC8 zy*jt(sh@cCSN4@l36GLpm;Q`>Iz8g;wau`1VxJjRcF**aLtT_t>2tN9f%1;U7sf#Z zwR@^cd&c3v?a>+Sf0Ms|--|$}lr_svFdPbW2HL>_cZQNvA$E8Ilz1k7)FdH*QcL^f zR9RVEFtx-7vOj8yBznjZ1G|1m0^rMiBu!2DW4I9{IUkGu0U7);TYF!#3Q~%P10$wI zYbT6^5kqM$j1Dmb6)5Rpmmn#z^DsfEMihWWTt+iz3c-#cNKObL4Q0g%Bk*5P%@-*I zBUEthfB}$HoC-_>2WqAJM<16094stHd_WO^aiL$6psINYgAG6`K4>8tp>FB#!#AMc zvWtumbJ%EmJjO6ROsZ`Tvm&qg?t<7LApYm=5fDFcf+s^pquk&dP$76$vmYwJ7d2I( z!rg7nR!lSEgT{}qgIRhaF%TyP|2tTefMpaCzXqEKp#|9@ih!L!))4|QEvSW0ufxQk z!h1285TKlA79eLn18ae#~gPZ}jZ{DUqBg#V^f%YqqOHnL)a8zbn$x0vDRT&mJ_aoxa^~mizVX=69-h z!iH;w)LsEE_1VP(7!BN!5o0JFIN~tk?dO^2s>0DSC3lsEi|tmNTt0tGjO%Y)F?x|f ztCsw6KsqtZ_6Ka@RzYuamM!m%<7YCs}j0MSFOzXWA)D_NvUhX1_VK-iMd35S#aHh0VsS z(MKkqXLa|MT3q;B{g?+aPW{>BB=Pzs(w(Mkg%dt0DRY`Z~(X+4mY8T%tEw%ZZBPhIK z8)|BCjQ5|q=Q#_~LqQ_v;{%a1qCw=G}VXDSHMx zsndmBzZfMAaz9n$tVKH|*wCi*tVXYj()Wwgk?)Zi@{HYf%i-E0t9>;}i!vQ5d&@9E z^Zdur;(6kd3jFcRRkj+9bfxWT^04g*;R~XoR_`0;w7=wvG>zqs;NNx65;qo)RbKDD z>`N0nJ2^kLSr}8@2ebEdpB#Mg!Tc>hqiNCi8fPlm(r!l4TaJY;ufhki^L0|+1aqdX ztfs!=E=Jz2QtO+#H#YQ0;`9JT`+4a|mboQsQF)Ee=Yj0AofYLT$n}$;7xc=t4J!JG zIONldhMz``8ONs>y?+dqFc@fX9(kW9mt&lGe>aS3^OFcJ@kUU0{)KI)?(1K>h`X-D zb_6SI46Z)QHEZsWI&#s=rOoatQLXY0k|~LA>*0$COIOy-PFY2QFKwieq6SC{;bhWu zOskqjoP8?ztHRSc_3~+~-^{;VPWydSA@^dWz~_99;|#%gYTNL{Mzjk*qRFD3Ik(mE z)9cW?S6>yJR6jQVwbns=FHA0KXiT5D_k5-dPhOO(=@Grr9)GFbbkT2{;$)(yMZhk?>K`Q63pLTiEGW=dG& zVdG1p$F;nD_rOcUG)exIv^Oi?GGV#ouy0+YsC{EKSw)7fL17jp`>n^m#R~SqA;`#e zHhh=}uGEfGz#7Ux73H_<$vHR_aB+M{JVWOT{Dg`*Kv+tdFB-RRelEnPQ7)HF<6u zyzWh_Ghz_;c4(3r#+G+stz%502JBxAEeN%!yc%d9;(MY))Yr{DG|DSNv1;gR@S)Eu zb{Qw2%b)e-%|(fP$5<9G22}8e53(yoiv>6?z0-)_KCIQvx?Wn%&2rg?y{Ps(CDf5O zOoP9er4<;`D;6vnrpWRTttKPBSvd8T< z>>c1%QOsU{>ft5C^6tZCh7XcNd0k0M=xGd|fl_qyjh>}1ZuJBwfw~WqNA4Xhe{J0o zq}h&l(CtX$pT`|~qg6eMQhCff?=tb|PQAmPRQO3;+R5===9>S9&gOat-I%AYn$-^- z_Bd?bEKvJ5^G_3~9OpQEkGiAM818>I>6eyoed=MY6I)XhmV5klm^yj+c@=6z4MdSzq9K;QJAoSC{-Verh(7Oofyi4`kHjpJ1D;<5l7a^F78| z8v7VkR`cMhXr!h-J>C;8l==oOcj^abAzQK3i!--AEI+SfLJ3U~9 zh+n{85x*CV`!n(|iZ|odbU{jh`F&M7EjD|l=lPFppFiTet3>>)o9G#mc%{&bO;n35 zo!$Em6`L{KB zcdZJ0WBK%n%Vm5Nm;CRIw+EY(c4i3&WmIdZ*sPw6gI`q{YjLo*s6q1soNkLGri!+@ zzB@0eEP3rZy}e^5r5{kC2&_8U8C?mYB{J+gfyWp2l5f0}^}~4$C~e#7RFOE9^&*d* zJYTm6Jip^yI$c8ZqKVq6EaYiX?j66Z=>=M&W3!)`It}>B0?X{4r;Ilr(SEWS^6gcg zSSx=%XoooeWdF+$$tmuMZn>Se0IogDn+nswIV^o~daIn|Nz1D9Z7B7f8su^I$Z=It zx_3r(y`z%bsVcc|EX`;B_@>u{V*%%M)+BSB4(mH{IS5#EzH7=ZlKpgkAg_Hf_tAmf zH$^)X)uTqzGRvTb#n`I}QbZ{%bCDz=y%lUThuh&fidNZ9`3(6F~0B@s#vMU?xKsw0=eJ`l5)r7nM=NF_BNFp7T9y(RZ* zP4}nG8_UFP@gzHx(e>Kxtcjg~-Oz3Ed9}~K4ckKYo;9@vwcI1u9{!kRiN#>id3*q4 zymwy4|D2$2nI1`hh}*L-wEKr{HlI&4s(RvWvekC(hFVz99ZE@uJnFCWj_r!N>Nk1i z;OVSqg>)K)j{rM`bZ7sFcS1Hx=gwnYG5t#1ks3x)((-$Qaj2|_&r=q=p4S3~l&sGE zSiq*tW^iuuDf$?Co2g~=05zZ2T;sqelQV0#ZSJsmWXgSuUsmKEh3t>tz0<8Re@_qxbZmQ!gS4!^i2nW<$cw~SiNikH2t z#VjGwV7SbR;^{7mlT4rE2#>OL(}ZY9?W&t1?Y5c_oSl5TduW@0BzEaEyPLnIjdzmQ zFs&oqFkQSv%-wLhQF45Bh&o-I#C0Ceb$)PQX-JP3%%3}O=S1(UTV9%fC#!h$fq|tW zdweBq)}{68(r>z}&xUCP*F}e{4?oG`rLZ`Wn_sBCm$bu(s?vq#5)CmMH&r41$sJcVUEv-LZ%L0VmX^+8VFi#wDJJ?A zd@A6E1DIZp2f>b^Y{-6ElL1PCA|&9{Pywa^=ZEs6_aQJ*D63j6g}+C?dS4^LfsliE z>Kowc&`+$>R}c&k&%ss14M<9)7cK>9b{~SXK{b*Ka5hNFaT>-4F|kwVr} z=3$MH)Q$ms4XQCRMCd^U{bkrks4!s!Pk;&=bnvI=YFtd<_ozT=T3p&c-$npiJ_rTy z#A(Sw;A#DgGrUcnKX#2-jW(vrs){cE`-FtcYxvF+pMmC zS_U=XvN* zKRuBKJ>Z|eXAkbo0_?^}?!;DE!W2Iw6Ivs)ACehIcM+-wTFsH?ffE&Q}i(z%YaB*yxO{=llQ4pg(^BD14DuAsKT@ z*34D#X-Oe578c@+OyAdkWwa8IguqNZk`t0aI;YV@gLY;>J2Gcvc#qLC>_E&DBo&Y) z0K*5i{E^gX*L&*$Hl6Jhf}bek0mL|PdLSoKhZMl|LbBpsr-lKoPcHHR^F834vLz$& z0B0{GJ!GyG{*vSpXzl&>lsC`k&LG?*##hA232-E)sbjnxP;N z>0-hluKsW~U_@3A8faNx5P8RpLBOwzsDL>1*!0WlQA38cpI49~LBq}9_?VnAJkI)8 zhzKw>LkdFt*%?}}pI{?nLB~-$!(YaZ;ctyXazU(S4#Zs_K-D;)TS%N?Wx0%D6$(Kz zK&)#rZJ%~PRz;9i@eHd84~F$(G?ES7Q^jboKX0z0D>3d77mZ{<16N}}z)R>~;BE|( z4-M3Zg22@Ee*wrjaysFcfG;M0Jos?R=GIHlJ`*_GXFcS#1SFUuf#3xw-z{nf|jidyCfQlRj0AG%|h_=t_4pxRJVk&QxHa>*dnRxK|s%_M3xm;Aov0 z!vo}Bekx#73Qh?Gnqn3kwmGPd*A#-mC$2<*kvWnBnvn?lNi#gqC!P}huQplpf7x3d zkmrC$NpKoq(^?M`3@RIS3==>HkpLY;+G8ByNh83KOVw_Q*S*vDL$|l zrxaLN!e{J-M*g)&1DM$T@we3EP+A1Y{}X(;%72ERJRZZJmxPJEx6rQeCIww72$Xmu zS)is#-Mnc+1dd!A*hk|t^66<9Rxmv9LH-2srt5y9!48D#l%3BnGPnFHt${2 z^#ql~5ny2{pHU=Jh~bYX17i~G0rEU#m|gZuK?-O%2b9r1!_Qjum!Agcet^7ywtOQ5 zw0x}ugJ{!(VKFNN$&E&4(R<02H)!PVULu7CHZ4IQu=Za7j0sf0JsueO-$hRO5M;?{ zf>;31vB-d150Rp1VJGzRw{3?IFfPyltqGVRpl}5NWH%aMN5478f#xo<1uHcMFqP;R z=739U2x_+f_rBbjeyR*X{OC=j#RQBm=uHHz?5aJe%y$N(%-R0W=7H0`{)qq_D`C*1 z&Ia1R&uCUbV8ja(BsaW3`Soo~r8_&E8Q28R@6eztvW6$bf(q_Xf~SQujS1Yx@Ix*F zZFn1|m~AKFKB(@|4+uW~z zqy>@2mz-}Fd|@j1pcqbvc`k3m;(kDltz#M(jkB1 zjfUhH4jniS%-}c}o*7XcXAHmPB%BPW?|@x^A}iFV;+P@pZg3fTfUb2`Xu|^|q+^4; z4zd3Gv)KQ3U&I?dQqVR@1zfj9GNRpB>j~IWr8^h|JV$Y&E43eil~{q8%GgJLe7o7i zn9Lb$#CtH>shw&0W(bBA{Vg)EX@p?>cRP1d%s<@+TYU;1_^zK3>JNj2&zSG$w(kwg&+G?5T|in5EVshPv&c6b^8Lc1pl?bGC6_ z*=R@Ahq$tiql?gE$N`2w#F^F*<)aM>0^mxd24>vAxpXkng4z>c0=DPyIR=LI`C${} zRdh>F&`9g6{~$?=nwX;u8h&gB!UU!0wWR>`Z)*|*(i70x1UN#U9{bAwt~x68kA={o zV%-5m{9j{KLJPHIVcyQMGG1E z?_-3s`OtVD#E*6gw-p#8K9z zgKKaFT!Zj4{2_Cgp$AWT(1~>u9p$AzVk-Z6NCAb^BV!Or-}nzAZHc4+h96)ivKD>V zuinJKP`I9u0MBOs94ph%d0a0T{r|*W9<;+ap(p?QE~XMZ&s~Pb!{Qyoz8!ctS!em* zluqC)deFH6mNNJS;JzisU0MfA|6(R!uSr}Ji!8ROG{s)oa2O~P# z{h=;Bm!LLmK)VA878df%_$$-?75bCWMfLuPtB@qwF86UQ=uG+G?48X8QRgv|WkVo8 zTlgQ($dV2>tp+`o1#G;+8B4j$7=9?%42bv>q#A~KtdGgtWT$5 z^Z+R#<`CeFPBV8oFi89{h_rejNzu=bSaIxl(AAgFkC32fL4;oP?Y!tpC}ku7Zbl-Z zc~?MZ5>otVL|Xy|^REy)l&FC1NI@Us9JMe2 zI33V~4lVRqEeMJ3518MBH;`hgkD4b2pyCKn@!8lgppRkgnTIh0LT;F?hrf#$Uz|n4)gY6{R zM$ygxAPmOf()pXuO7C(IOMp`-0>+xN`G}k)MhMJ*NP%Ea@X?CaU9j^XY%tZ}sg)2& byo;ozJqPv{3#*H~i~I%~K9=Pj^v?Bv|HR`y From f7950ce5d8f7fc93bf2bb46b07592894ffbdc39e Mon Sep 17 00:00:00 2001 From: Alex Kondrashov Date: Wed, 10 Apr 2024 10:17:54 +0300 Subject: [PATCH 2/2] Add a test for invalid value labels --- .../ncrn/ced2ar/stata/StataReaderFactory.java | 77 ++ .../ncrn/ced2ar/stata/impl/Dta117Reader.java | 477 +++++++++++++ .../ncrn/ced2ar/stata/impl/Dta118Reader.java | 231 ++++++ .../ncrn/ced2ar/stata/impl/DtaReader.java | 670 ++++++++++++++++++ ...118Test.java => Invalid118FormatTest.java} | 11 +- target/Extract2DDI.jar | Bin 5477693 -> 5477459 bytes 6 files changed, 1460 insertions(+), 6 deletions(-) create mode 100644 src/main/java/edu/cornell/ncrn/ced2ar/stata/StataReaderFactory.java create mode 100644 src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta117Reader.java create mode 100644 src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta118Reader.java create mode 100644 src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/DtaReader.java rename src/test/java/edu/cornell/ncrn/ced2ar/ddigen/{Format118Test.java => Invalid118FormatTest.java} (76%) diff --git a/src/main/java/edu/cornell/ncrn/ced2ar/stata/StataReaderFactory.java b/src/main/java/edu/cornell/ncrn/ced2ar/stata/StataReaderFactory.java new file mode 100644 index 0000000..61abd70 --- /dev/null +++ b/src/main/java/edu/cornell/ncrn/ced2ar/stata/StataReaderFactory.java @@ -0,0 +1,77 @@ +package edu.cornell.ncrn.ced2ar.stata; + +import java.io.IOException; + +import org.apache.log4j.Logger; + +import edu.cornell.ncrn.ced2ar.stata.exceptions.InvalidDtaFormatException; +import edu.cornell.ncrn.ced2ar.stata.impl.Dta113Reader; +import edu.cornell.ncrn.ced2ar.stata.impl.Dta114Reader; +import edu.cornell.ncrn.ced2ar.stata.impl.Dta115Reader; +import edu.cornell.ncrn.ced2ar.stata.impl.Dta117Reader; +import edu.cornell.ncrn.ced2ar.stata.impl.Dta118Reader; + +public class StataReaderFactory { + private static final Logger logger = Logger.getLogger(StataReaderFactory.class); + public StataReader getStataReader(String stataFile) throws IOException, InvalidDtaFormatException{ + try{ + return (new Dta115Reader(stataFile)); + } + catch(InvalidDtaFormatException IDFE){ + logger.info("Stata Data file " + stataFile + " is not a Format 115."); + } + try{ + return (new Dta114Reader(stataFile)); + } + catch(InvalidDtaFormatException IDFE){ + logger.info("Stata Data file " + stataFile + " is not a Format 114."); + } + try{ + return (new Dta113Reader(stataFile)); + } + catch(InvalidDtaFormatException IDFE){ + logger.info("Stata Data file " + stataFile + " is not a Format 113."); + } + try{ + return (new Dta117Reader(stataFile)); + } + catch(InvalidDtaFormatException IDFE){ + logger.info("Stata Data file " + stataFile + " is not a Format 117"); + } + try{ + return (new Dta118Reader(stataFile)); + } + catch(InvalidDtaFormatException IDFE){ + logger.info("Stata Data file " + stataFile + " is not a Format 113, 114, 115, 117 or 118 stata file. ie This datafile is not stata v8, v10, v12, v13 or v14"); + throw IDFE; + } + + } + + + public static void main(String argc[]) throws Exception{ + StataReaderFactory factory = new StataReaderFactory(); + // this file is failing on reading value labels' + //StataReader SR = factory.getStataReader("C:\\java\\info\\Data\\STATA\\Expert-Survey-STATAVersion-2.2.dta"); + //StataReader SR = factory.getStataReader("C:\\java\\info\\Data\\STATA\\dc2010UR1_ALL_VARS.DTA"); + //StataReader SR = factory.getStataReader("C:\\java\\info\\Data\\Michelle Data\\04275-0001-Data.dta"); + //StataReader SR = factory.getStataReader("C:\\java\\info\\Data\\Michelle Data\\07948-0001-Data.dta"); + //StataReader SR = factory.getStataReader("C:\\java\\info\\Data\\Michelle Data\\29662-0001-Data.dta"); + //StataReader SR = factory.getStataReader("C:\\java\\info\\Data\\STATA\\auto2.dta"); + //StataReader SR = factory.getStataReader("C:\\java\\info\\Data\\STATA\\statacarwithLabel.dta"); + //"C:\\java\\info\\Data\\STATA\\statacarwithLabel.dta" 117 + + //stataFile.readStataFile("C:\\java\\info\\Data\\STATA\\IMFFinReform.dta"); + //stataFile.readStataFile("C:\\java\\info\\Data\\STATA\\me2010ur1_all_vars.DTA"); + //stataFile.readStataFile("C:\\java\\info\\Data\\STATA\\dc2010UR1_ALL_VARS.DTA"); + //stataFile.readStataFile("C:\\java\\info\\Data\\STATA\\p10i6.dta"); + //stataFile.readStataFile("C:\\java\\info\\Data\\STATA\\ssb_v6_0_synthetic1_1.dta"); + //StataFile stataFile = new StataFile("C:\\java\\info\\Data\\STATA\\statacar.dta"); + //SR.dumpData(); + //System.out.println(SR.getObservation(7)); + //System.out.println(SR.getObservation(8)); + + } + + +} diff --git a/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta117Reader.java b/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta117Reader.java new file mode 100644 index 0000000..c270024 --- /dev/null +++ b/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta117Reader.java @@ -0,0 +1,477 @@ +package edu.cornell.ncrn.ced2ar.stata.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import edu.cornell.ncrn.ced2ar.stata.StataReader; +import edu.cornell.ncrn.ced2ar.stata.exceptions.InvalidDtaFormatException; + +/** + * This class reads STATA data file of format 117. + * + * + Stata versions and file formats + Format Current as of + --------------------------------------- + 118 Stata 14 http://www.stata.com/help.cgi?dta + 117 Stata 13 http://www.stata.com/help.cgi?dta + 116 internal; never released + 115 Stata 12 http://www.stata.com/help.cgi?dta_115 + 114 Stata 10 & 11 http://www.stata.com/help.cgi?dta_114 + 113 Stata 8 & 9 http://www.stata.com/help.cgi?dta_113 + -------------------------------------- + + * USAGE + * StataFile stataFile = new StataFile("C:\\java\\info\\Data\\STATA\\IMFFinReform.dta"); + stataFile.getObservation(10); + * + *@author Cornell University, Copyright 2012-2015 + *@author Venky Kambhampaty + * + *@author Cornell Institute for Social and Economic Research + *@author Cornell Labor Dynamics Institute + *@author NCRN Project Team + */ + +public class Dta117Reader extends DtaReader implements StataReader{ + private static final Logger logger = Logger.getLogger(Dta117Reader.class); + + protected long startOfStataDataSection; + protected long startOfMapSection; + protected long startOfVariableTypesSection; + protected long startOfVarNamesSection; + protected long startOfSortListSection; + protected long startOfFormatsSection; + protected long startOfValueLabelNamesSection; + protected long startOfVariableLablesSection; + protected long startOfCharacteristicsSection; + protected long startOfDataSection; + protected long startOfStrlsSection; + protected long startOfValueLabelsSection; + protected long startOfEndStataDataSection; + protected long endOfFile; + + + public Dta117Reader(String stataFile) throws IOException,InvalidDtaFormatException{ + setDataFile(stataFile); + try{ + openDtaFile(); + readHeader(); + isValidFormat(); + readMap(); + readVariables(); + } + finally{ + closeDtaFile(); + } + } + + @Override + protected void move2ObservationStart(long observationNumber) throws IOException{ + stataDataRAF.seek( startOfDataSection+"".length() + + getObservationLength() * (observationNumber-1)); + } + + + /** + * This method reads variable information. + * Variable information includes + * variable name + * variable type + * variable format + * variable label + * + * @throws IOException + */ + protected void readVariables() throws IOException{ + List dtaVariables = new ArrayList(); + stataDataRAF.seek(startOfVariableTypesSection+"".length()); + + // Read Variable Type information + for(int i=0;i".length()); + for(int i=0;i".length()); + for(int i=0;i".length()); + for(int i=0;i".length()); + for(int i=0;i".length()); + readValueLabels(); + // for debugging + for(DtaVariable dtaVariable:dtaVariables){ + logger.debug(dtaVariable); + } + } + + /** + * Reads the map of the data file. Map of a dta117 file points for + * 14 distinct section start locations. This method populates these + * locations as instance variables + * + # file position of the start of the + ----------------------------------------------- + 1. , definitionally 0 + 2. + 3. + 4. + 5. + 6. + 7. + 8. + 9. + 10. + 11. + 12. + 13. + 14. end-of-file + ----------------------------------------------- + * + */ + protected void readMap() throws IOException{ + int startPositionOfFilePositions= "".length(); + stataDataRAF.seek(startPositionOfFilePositions+stataDataRAF.getFilePointer()); + + + byte b[] = new byte[8]; + stataDataRAF.read(b); + startOfStataDataSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfStataDataSection ); + + b = new byte[8]; + stataDataRAF.read(b); + startOfMapSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfMapSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfVariableTypesSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfVariableTypesSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfVarNamesSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfVarNamesSection); + + + b = new byte[8]; + stataDataRAF.read(b); + startOfSortListSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfSortListSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfFormatsSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfFormatsSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfValueLabelNamesSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfValueLabelNamesSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfVariableLablesSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfVariableLablesSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfCharacteristicsSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfCharacteristicsSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfDataSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfDataSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfStrlsSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfStrlsSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfValueLabelsSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfValueLabelsSection); + + b = new byte[8]; + stataDataRAF.read(b); + startOfEndStataDataSection = getLongValue(b,this.dtaHeader.isLittleEndian()); + logger.debug("Start of " + startOfEndStataDataSection); + + b = new byte[8]; + stataDataRAF.read(b); + endOfFile = getLongValue(b,dtaHeader.isLittleEndian()); + logger.debug("end of File " + endOfFile); + } + + + /** + * Reads the header record of the stata file. + * File pointer is assumed to be at the start location of Header. + * After successfule reading of header File position will + * be at the start of (Next section of the dta file) + * Format 117 header + +
+ 117 + MSF + 0002 + 0001 + + 1110 Jul 2013 14:23 +
+ * @throws IOException + */ + protected void readHeader() throws IOException{ + DtaHeader dtaHeader = new DtaHeader(); + //Read File Format + int startPositionOfFileFormat = "
".length(); + stataDataRAF.seek(startPositionOfFileFormat+stataDataRAF.getFilePointer()); + byte b[] = new byte[3]; + stataDataRAF.read(b); + String fileFormatId = getStringValue(b); + logger.debug("File format Id = " +fileFormatId); + dtaHeader.setDtaFileFormat(Byte.parseByte(fileFormatId)); + + //Read Byte Order + int startPositionOfByteOrder= "".length(); + stataDataRAF.seek(startPositionOfByteOrder+stataDataRAF.getFilePointer()); + b = new byte[3]; + stataDataRAF.read(b); + String byteOrder = getStringValue(b); + logger.debug("byteOrder = " +byteOrder); + if(byteOrder.equals("LSF")){ + dtaHeader.setByteOrder((byte)2); + } + else if(byteOrder.equals("MSF")){ + dtaHeader.setByteOrder((byte)1); + } + else{ + throw new RuntimeException("Unknown byte order: " + byteOrder); + } + + //Read Number of Variables + int startPositionOfNumberOfVariables= "".length(); + stataDataRAF.seek(startPositionOfNumberOfVariables+stataDataRAF.getFilePointer()); + b = new byte[2]; + stataDataRAF.read(b); + int numberOfVariables = getShortValue(b, dtaHeader.isLittleEndian()); + dtaHeader.setNumberOfVariables(numberOfVariables); + logger.debug("numberOfVariables = " +numberOfVariables); + + + //Read Number of Variables + int startPositionOfNumberOfObservations= "".length(); + stataDataRAF.seek(startPositionOfNumberOfObservations+stataDataRAF.getFilePointer()); + b = new byte[4]; + stataDataRAF.read(b); + int numberOfObservations = getUnsignedIntValue(b, dtaHeader.isLittleEndian()); + dtaHeader.setNumberOfObservations(numberOfObservations); + logger.debug("numberOfObservations = " +numberOfObservations); + + //Read Data Label + int startPositionOfDataLabel= "".length(); + stataDataRAF.seek(startPositionOfTimestamp+stataDataRAF.getFilePointer()); + b = new byte[1]; + stataDataRAF.read(b); + //int sizeOfTimestamp = getByteValue(b[0], dtaHeader.isLittleEndian()); + int sizeOfTimestamp = getUnsignedIntValue(b, dtaHeader.isLittleEndian()); + b = new byte[sizeOfTimestamp]; + stataDataRAF.read(b); + String timeStamp = getStringValue(b); + dtaHeader.setTimeStamp(timeStamp); + logger.debug("timeStamp = " +timeStamp); + + setDtaHeader(dtaHeader); + logger.debug("dtaHeader = " +dtaHeader); + + int startPositionOfMap= "
".length(); + stataDataRAF.seek(startPositionOfMap+stataDataRAF.getFilePointer()); + } + + /** + * @throws InvalidDtaFormatException + */ + protected void isValidFormat() throws InvalidDtaFormatException{ + if( dtaHeader.getDtaFileFormat() != 117) + throw new InvalidDtaFormatException("This reader can only read format 117 Stata Files."); + } + + // getter and setter + public long getStartOfStataDataSection() { + return startOfStataDataSection; + } + + public void setStartOfStataDataSection(long startOfStataDataSection) { + this.startOfStataDataSection = startOfStataDataSection; + } + + public long getStartOfMapSection() { + return startOfMapSection; + } + + public void setStartOfMapSection(long startOfMapSection) { + this.startOfMapSection = startOfMapSection; + } + + public long getStartOfVariableTypesSection() { + return startOfVariableTypesSection; + } + + public void setStartOfVariableTypesSection(long startOfVariableTypesSection) { + this.startOfVariableTypesSection = startOfVariableTypesSection; + } + + public long getStartOfVarNamesSection() { + return startOfVarNamesSection; + } + + public void setStartOfVarNamesSection(long startOfVarNamesSection) { + this.startOfVarNamesSection = startOfVarNamesSection; + } + + public long getStartOfSortListSection() { + return startOfSortListSection; + } + + public void setStartOfSortListSection(long startOfSortListSection) { + this.startOfSortListSection = startOfSortListSection; + } + + public long getStartOfFormatsSection() { + return startOfFormatsSection; + } + + public void setStartOfFormatsSection(long startOfFormatsSection) { + this.startOfFormatsSection = startOfFormatsSection; + } + + public long getStartOfValueLabelNamesSection() { + return startOfValueLabelNamesSection; + } + + public void setStartOfValueLabelNamesSection(long startOfValueLabelNamesSection) { + this.startOfValueLabelNamesSection = startOfValueLabelNamesSection; + } + + public long getStartOfVariableLablesSection() { + return startOfVariableLablesSection; + } + + public void setStartOfVariableLablesSection(long startOfVariableLablesSection) { + this.startOfVariableLablesSection = startOfVariableLablesSection; + } + + public long getStartOfCharacteristicsSection() { + return startOfCharacteristicsSection; + } + + public void setStartOfCharacteristicsSection(long startOfCharacteristicsSection) { + this.startOfCharacteristicsSection = startOfCharacteristicsSection; + } + + public long getStartOfDataSection() { + return startOfDataSection; + } + + public void setStartOfDataSection(long startOfDataSection) { + this.startOfDataSection = startOfDataSection; + } + + public long getStartOfStrlsSection() { + return startOfStrlsSection; + } + + public void setStartOfStrlsSection(long startOfStrlsSection) { + this.startOfStrlsSection = startOfStrlsSection; + } + + public long getStartOfValueLabelsSection() { + return startOfValueLabelsSection; + } + + public void setStartOfValueLabelsSection(long startOfValueLabelsSection) { + this.startOfValueLabelsSection = startOfValueLabelsSection; + } + + public long getStartOfEndStataDataSection() { + return startOfEndStataDataSection; + } + + public void setStartOfEndStataDataSection(long startOfEndStataDataSection) { + this.startOfEndStataDataSection = startOfEndStataDataSection; + } + + public long getEndOfFile() { + return endOfFile; + } + + public void setEndOfFile(long endOfFile) { + this.endOfFile = endOfFile; + } +} diff --git a/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta118Reader.java b/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta118Reader.java new file mode 100644 index 0000000..735afb1 --- /dev/null +++ b/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/Dta118Reader.java @@ -0,0 +1,231 @@ +package edu.cornell.ncrn.ced2ar.stata.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import edu.cornell.ncrn.ced2ar.stata.StataReader; +import edu.cornell.ncrn.ced2ar.stata.exceptions.InvalidDtaFormatException; + +/** + + * strLs not supported + * This class reads STATA data file of format 118. + * + * Differences between Format 117 and format 118 + * 1. Number of variables is a 4 byte unsigned int value in 117. 8 byte unsigned int value in 118 + * 2. Lable length is a 1 byte unsigned int value in 117. 2 byte unsigned int value in 118 + * + Stata versions and file formats + Format Current as of + --------------------------------------- + 118 Stata 14 http://www.stata.com/help.cgi?dta + 117 Stata 13 http://www.stata.com/help.cgi?dta + 116 internal; never released + 115 Stata 12 http://www.stata.com/help.cgi?dta_115 + 114 Stata 10 & 11 http://www.stata.com/help.cgi?dta_114 + 113 Stata 8 & 9 http://www.stata.com/help.cgi?dta_113 + -------------------------------------- + + * USAGE + * StataFile stataFile = new StataFile("C:\\java\\info\\Data\\STATA\\IMFFinReform.dta"); + stataFile.getObservation(10); + * + *@author Cornell University, Copyright 2012-2015 + *@author Venky Kambhampaty + * + *@author Cornell Institute for Social and Economic Research + *@author Cornell Labor Dynamics Institute + *@author NCRN Project Team + */ + +public class Dta118Reader extends Dta117Reader implements StataReader{ + + private static final Logger logger = Logger.getLogger(Dta118Reader.class); + + public Dta118Reader(String stataFile) throws IOException,InvalidDtaFormatException{ + super(stataFile); + } + /** + * @throws InvalidDtaFormatException + */ + protected void isValidFormat() throws InvalidDtaFormatException{ + if( dtaHeader.getDtaFileFormat() != 118) + throw new InvalidDtaFormatException("This reader can only read format 118 Stata Files."); + } + + /** + * Reads the header record of the stata file. + * File pointer is assumed to be at the start location of Header. + * After successfule reading of header File position will + * be at the start of (Next section of the dta file) + * + * Number of observations in format 118 is 8 byte unsigned int + * Number of observations in format 117 is 4 byte unsigned int + * Format 118 header + +
+ 117 + MSF + 0002 + 00000001 + + 1110 Jul 2013 14:23 +
+ * @throws IOException + */ + + protected void readHeader() throws IOException{ + DtaHeader dtaHeader = new DtaHeader(); + //Read File Format + int startPositionOfFileFormat = "
".length(); + stataDataRAF.seek(startPositionOfFileFormat+stataDataRAF.getFilePointer()); + byte b[] = new byte[3]; + stataDataRAF.read(b); + String fileFormatId = getStringValue(b); + logger.debug("File format Id = " +fileFormatId); + dtaHeader.setDtaFileFormat(Byte.parseByte(fileFormatId)); + + //Read Byte Order + int startPositionOfByteOrder= "".length(); + stataDataRAF.seek(startPositionOfByteOrder+stataDataRAF.getFilePointer()); + b = new byte[3]; + stataDataRAF.read(b); + String byteOrder = getStringValue(b); + logger.debug("byteOrder = " +byteOrder); + if(byteOrder.equals("LSF")){ + dtaHeader.setByteOrder((byte)2); + } + else if(byteOrder.equals("MSF")){ + dtaHeader.setByteOrder((byte)1); + } + else{ + throw new RuntimeException("Unknown byte order: " + byteOrder); + } + + //Read Number of Variables + int startPositionOfNumberOfVariables= "".length(); + stataDataRAF.seek(startPositionOfNumberOfVariables+stataDataRAF.getFilePointer()); + b = new byte[2]; + stataDataRAF.read(b); + int numberOfVariables = getShortValue(b, dtaHeader.isLittleEndian()); + dtaHeader.setNumberOfVariables(numberOfVariables); + logger.debug("numberOfVariables = " +numberOfVariables); + + + //Read Number of Variables + int startPositionOfNumberOfObservations= "".length(); + stataDataRAF.seek(startPositionOfNumberOfObservations+stataDataRAF.getFilePointer()); + b = new byte[8]; + stataDataRAF.read(b); + long numberOfObservations = getUnsignedLongValue(b, dtaHeader.isLittleEndian()); + dtaHeader.setNumberOfObservations(numberOfObservations); + logger.debug("numberOfObservations = " +numberOfObservations); + + //Read Data Label + int startPositionOfDataLabel= "".length(); + stataDataRAF.seek(startPositionOfTimestamp+stataDataRAF.getFilePointer()); + b = new byte[1]; + stataDataRAF.read(b); + int sizeOfTimestamp = getUnsignedIntValue(b, dtaHeader.isLittleEndian()); + b = new byte[sizeOfTimestamp]; + stataDataRAF.read(b); + String timeStamp = getStringValue(b); + dtaHeader.setTimeStamp(timeStamp); + logger.debug("timeStamp = " +timeStamp); + + setDtaHeader(dtaHeader); + logger.debug("dtaHeader = " +dtaHeader); + + int startPositionOfMap= "
".length(); + stataDataRAF.seek(startPositionOfMap+stataDataRAF.getFilePointer()); + } + + /** + * This method reads variable information. + * Variable information includes + * variable name + * variable type + * variable format + * variable label + * + * @throws IOException + */ + protected void readVariables() throws IOException{ + List dtaVariables = new ArrayList(); + stataDataRAF.seek(startOfVariableTypesSection+"".length()); + + // Read Variable Type information + for(int i=0;i".length()); + for(int i=0;i".length()); + for(int i=0;i".length()); + for(int i=0;i".length()); + for(int i=0;i".length()); + readValueLabels(); + // for debugging + for(DtaVariable dtaVariable:dtaVariables){ + logger.debug(dtaVariable); + } + } + + + +} diff --git a/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/DtaReader.java b/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/DtaReader.java new file mode 100644 index 0000000..00e0911 --- /dev/null +++ b/src/main/java/edu/cornell/ncrn/ced2ar/stata/impl/DtaReader.java @@ -0,0 +1,670 @@ +package edu.cornell.ncrn.ced2ar.stata.impl; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.apache.log4j.Logger; + +import edu.cornell.ncrn.ced2ar.stata.exceptions.InvalidDtaFormatException; + +/** + * This class is base class for stata readers. + * This class provides some basic methods to read stata data file + * Stata data files are formatted differently for different versions of the stata + * Stata data file that confirms to format 113, 114, 115 and 117 + * + Stata versions and file formats + Format Current as of + --------------------------------------- + 118 Stata 14 http://www.stata.com/help.cgi?dta + 117 Stata 13 http://www.stata.com/help.cgi?dta + 116 internal; never released + 115 Stata 12 http://www.stata.com/help.cgi?dta_115 + 114 Stata 10 & 11 http://www.stata.com/help.cgi?dta_114 + 113 Stata 8 & 9 http://www.stata.com/help.cgi?dta_113 + -------------------------------------- + + *@author Cornell University, Copyright 2012-2015 + *@author Venky Kambhampaty + * + *@author Cornell Institute for Social and Economic Research + *@author Cornell Labor Dynamics Institute + *@author NCRN Project Team + */ + + +public abstract class DtaReader{ + private static final Logger logger = Logger.getLogger(DtaReader.class); + + public enum VALID_BYTE_VALUES{ + MIN_VALUE((byte)-127), + MAX_VALUE((byte)100); + private byte value; + private VALID_BYTE_VALUES(byte value){ + this.value = value; + } + public byte getValue(){ + return value; + } + } + public enum VALID_INT_VALUES{ + MIN_VALUE(-32767), + MAX_VALUE(32740); + private int value; + private VALID_INT_VALUES(int value){ + this.value = value; + } + public int getValue(){ + return value; + } + + } + public enum VALID_LONG_VALUES{ + MIN_VALUE(-2147483647), + MAX_VALUE(2147483620); + private long value; + private VALID_LONG_VALUES(long value){ + this.value = value; + } + public long getValue(){ + return value; + } + + } + public enum VALID_FLOAT_VALUES{ + MIN_VALUE(-1.701e+38f), + MAX_VALUE(+1.701e+38f); + private float value; + private VALID_FLOAT_VALUES(float value){ + this.value = value; + } + public float getValue(){ + return value; + } + + } + + public enum MISSING_DOUBLE_VALUES{ + MIN_VALUE((+8.988e+307)+1), + MAX_VALUE((+8.988e+307)+27); + private double value; + private MISSING_DOUBLE_VALUES(double value){ + this.value = value; + } + public double getValue(){ + return value; + } + } + + protected String dataFile; + protected RandomAccessFile stataDataRAF; + protected DtaHeader dtaHeader; + protected List dtaVariables; + + /** + * This method opens STATA data file + * @throws IOException + */ + public void openDtaFile() throws IOException{ + try{ + stataDataRAF = new RandomAccessFile(new File(dataFile),"r"); + } + catch(IOException ex){ + throw ex; + } + } + + /** + * This method Closes STATA data file + * @throws IOException + */ + public void closeDtaFile() throws IOException{ + if(stataDataRAF !=null) stataDataRAF.close(); + } + + + /** + * This method returns the length of observation by adding + * all the variable data type lengths + * @return long + */ + public long getObservationLength(){ + long lengthOfObservation = 0; + for(DtaVariable dtaVariable:dtaVariables){ + if(dtaVariable.isByte()){ + lengthOfObservation++; + } + else if(dtaVariable.isInt()){ + lengthOfObservation+=2; + } + else if(dtaVariable.isLong() || dtaVariable.isFloat()){ + lengthOfObservation+=4; + } + else if(dtaVariable.isDouble()){ + lengthOfObservation+=8; + } + else{ + lengthOfObservation+=dtaVariable.getVariableType(); + } + } + return lengthOfObservation; + } + + /** + * This method dumps the data to console (log4j configuration required) in a csv format + * Can print large amounts of data + * @throws IOException + */ + public void dumpData() throws IOException{ + try{ + openDtaFile(); + for(long l=1;l<=dtaHeader.getNumberOfObservations();l++){ + logger.debug(getObservationAsCSV(readObservation(l))+"\n"); + } + } + finally{ + this.closeDtaFile(); + } + } + + /** + * Can be memory intensive. Not recommended for huge data sets. + * @return list of all observations as a list of Strings in CSV format + * @throws IOException + */ + public List> getObservations() throws IOException { + try{ + openDtaFile(); + List> observations = new ArrayList>(); + for(long l=1;l<=dtaHeader.getNumberOfObservations();l++){ + observations.add(readObservation(l)); + } + return observations; + } + finally{ + closeDtaFile(); + } + } + + /** + * returns one observation as a CSV string. + * @param observationNumber + * @return CSV formatted string if the observationNumber is valid. An Empty string otherwise. + * @throws IOException + */ + public List getObservation(long observationNumber) throws IOException { + try{ + openDtaFile(); + return readObservation(observationNumber); + } + finally{ + closeDtaFile(); + } + } + + /** + * This method is a placeholder to move the file pointer to the start of the data section + * of the the stata file. Data section start calculation can vary between various versions + * of the stata file. + * + * @param observationNumber + * @throws IOException + */ + protected void move2ObservationStart(long observationNumber) throws IOException{ + throw new RuntimeException("This method should have been overridden"); + } + + + /** + * @param observation + * @return CSV version of observation + * @throws IOException + */ + protected String getObservationAsCSV(List observation) throws IOException{ + StringBuilder SB = new StringBuilder(""); + for(String s:observation){ + SB.append(s+","); + } + return SB.toString().substring(0,SB.toString().length()-1); + } + + /** + * Returns a string containing observation values in CSV format. + * Returns an empty string if the observation number is invalid + * This method is format specific + * @param observationNumber + * @return An Observation in CSV format; + * @throws IOException + */ + protected List readObservation(long observationNumber) throws IOException{ + List observation = new ArrayList(); + if(observationNumber <=0 || observationNumber > dtaHeader.getNumberOfObservations()){ + return observation; + } + else{ + move2ObservationStart(observationNumber); + } + + for(DtaVariable dtaVariable : dtaVariables){ + int variableType = dtaVariable.getVariableType(); + if(dtaVariable.isString()){ + byte b[] = new byte[variableType]; + stataDataRAF.read(b); + observation.add(getStringValue(b)); + } + else if(dtaVariable.isByte()){ + byte b[] = new byte[1]; + stataDataRAF.read(b); + int intValue =getByteValue(b[0], dtaHeader.isLittleEndian()); + if(intValue >= VALID_BYTE_VALUES.MIN_VALUE.value && intValue <= VALID_BYTE_VALUES.MAX_VALUE.value){ + observation.add(""+intValue); + } + else{ + observation.add("."); + //SB.append(".,"); + } + } + else if(dtaVariable.isInt()){ // two byte stata-integer is a short in java + byte b[] = new byte[2]; + stataDataRAF.read(b); + int intValue =getShortValue(b, dtaHeader.isLittleEndian()); + if(intValue >= VALID_INT_VALUES.MIN_VALUE.value && intValue <= VALID_INT_VALUES.MAX_VALUE.value){ + observation.add(""+intValue); + //SB.append(intValue+","); + } + else{ + observation.add("."); + //SB.append(".,"); + } + } + else if(dtaVariable.isLong()){ // 4 byte stata-long is an integer in java + byte b[] = new byte[4]; + stataDataRAF.read(b); + int intValue =getIntValue(b, dtaHeader.isLittleEndian()); + if(intValue >= VALID_LONG_VALUES.MIN_VALUE.value && intValue <= VALID_LONG_VALUES.MAX_VALUE.value){ + observation.add(""+intValue); + //SB.append(intValue+","); + } + else{ + observation.add("."); + //SB.append(".,"); + } + } + else if(dtaVariable.isFloat()){ + byte b[] = new byte[4]; + stataDataRAF.read(b); + float f; + if(dtaHeader.isLittleEndian()) + f = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getFloat(); + else + f = ByteBuffer.wrap(b).order(ByteOrder.BIG_ENDIAN).getFloat(); + + if(f >= VALID_FLOAT_VALUES.MIN_VALUE.value && f <= VALID_FLOAT_VALUES.MAX_VALUE.value){ + observation.add(""+f); + //SB.append(f+","); + } + else{ + observation.add("."); + //SB.append(".,"); + } + } + else if(dtaVariable.isDouble()){ + byte b[] = new byte[8]; + stataDataRAF.read(b); + double d; + if(dtaHeader.isLittleEndian()) + d = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getDouble(); + else + d = ByteBuffer.wrap(b).order(ByteOrder.BIG_ENDIAN).getDouble(); + + if(d >= MISSING_DOUBLE_VALUES.MIN_VALUE.value && d <= MISSING_DOUBLE_VALUES.MAX_VALUE.value){ + observation.add("."); + //SB.append(".,"); + } + else{ + observation.add(""+d); + //SB.append(d+","); + } + } + else{ + logger.info("Unable to find the datatype"); + + } + } + return observation; +// return SB.toString().substring(0,SB.toString().length()-1); + } + + + /** + * Returns the observations in as a list. + * Each element of the list represents one observation in csv format + * @param start Start of the observation Number + * @param end End of the observation number. + * @return + * @throws IOException + */ + public List> getObservations(long start, long end)throws IOException { + try{ + openDtaFile(); + List> observations = new ArrayList>(); + for(long l=start;l<=end;l++){ + observations.add(getObservation(l)); + } + return observations; + } + finally{ + closeDtaFile(); + } + } + + /** + * reads the value lables and adds them to appropriate variable(s) + * each value label is written + as + Contents len format comment + ------------------------------------------------------------------- + len 4 int length of value_label_table + labname 33 char \0 terminated + padding 3 + value_label_table len see next table + ------------------------------------------------------------------- + and this is repeated for each value label included in the file. The + format of the value_label_table is + Contents len format comment + ---------------------------------------------------------- + n 4 int number of entries + txtlen 4 int length of txt[] + off[] 4*n int array txt[] offset table + val[] 4*n int array sorted value table + txt[] txtlen char text table + ---------------------------------------------------------- + len, n, txtlen, off[], and val[] are encoded per byteorder. The maximum + length of txt[] for a label is 32,000 characters. Stata is robust to + datasets which might contain labels longer than this; labels which exceed + the limit, if any, will be dropped during a use. + * @throws IOException + */ + protected void readValueLabels() throws IOException{ + logger.debug("Start of Value Label Section: " + stataDataRAF.getFilePointer()); + while(true){ + byte b[] = new byte[4]; + stataDataRAF.read(b); + int lengthOfValueTable = getIntValue(b, dtaHeader.isLittleEndian()); + + b = new byte[33]; + stataDataRAF.read(b); + String valueLabelName = getStringValue(b); + + try { + b = new byte[3]; + stataDataRAF.read(b); + String padding = getStringValue(b); + + + b = new byte[4]; + stataDataRAF.read(b); + int numberOfEntries = getIntValue(b, dtaHeader.isLittleEndian()); + + b = new byte[4]; + stataDataRAF.read(b); + int textLength = getIntValue(b, dtaHeader.isLittleEndian()); + + int offsets[] = new int[numberOfEntries]; + for (int i = 0; i < numberOfEntries; i++) { + b = new byte[4]; + stataDataRAF.read(b); + offsets[i] = getIntValue(b, dtaHeader.isLittleEndian()); + } + int values[] = new int[numberOfEntries]; + for (int i = 0; i < numberOfEntries; i++) { + b = new byte[4]; + stataDataRAF.read(b); + values[i] = getIntValue(b, dtaHeader.isLittleEndian()); + } + + b = new byte[textLength]; + stataDataRAF.read(b); + String[] labels = new String[numberOfEntries]; + for (int i = 0; i < numberOfEntries; i++) { + if (offsets.length >= (i + 1)) { + byte[] text = Arrays.copyOfRange(b, offsets[i], b.length); + labels[i] = getStringValue(text); + } else { + byte[] text = Arrays.copyOfRange(b, offsets[i], offsets[i + 1]); + labels[i] = getStringValue(text); + } + } + //Add value label pairs to variables + for (DtaVariable dtaVariable : dtaVariables) { + if (dtaVariable.getVariableValueLabelName().equals(valueLabelName)) { + HashMap valueLabelMap = dtaVariable.getVariableValueLabels(); + for (int i = 0; i < values.length; i++) { + valueLabelMap.put("" + values[i], labels[i]); + } + } + } + if (dtaHeader.getDtaFileFormat() == 117) { + if (stataDataRAF.length() <= stataDataRAF.getFilePointer() + "
".length()) + break; + else + stataDataRAF.seek(stataDataRAF.getFilePointer() + "
".length()); + } else { + if (stataDataRAF.length() <= stataDataRAF.getFilePointer()) break; + } + } + catch(Exception ex){ + for (DtaVariable dtaVariable: dtaVariables){ + if(dtaVariable.getVariableValueLabelName().equals(valueLabelName)){ + logger.error("Error reading Value Labels for variable: " + dtaVariable.getName()); + throw ex; + } + } + logger.error("Error reading Value Labels"); + throw ex; + } + } + } + + /** + * This method calculates int value from the signed byte array. + * Byte Array size is expected to be 4 + * + * @param bytes + * @param isLittleEndian + * @return int value of the byte array + */ + protected int getIntValue(byte[] bytes, boolean isLittleEndian){ + int i; + if(isLittleEndian) + i = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + else + i = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt(); + return i; + } + + /** + * This method calculates long value from the signed byte array. + * Byte Array size is expected to be 8 + * + * @param bytes + * @param isLittleEndian + * @return long value of the byte array + */ + protected long getLongValue(byte[] bytes, boolean isLittleEndian){ + long l; + if(isLittleEndian) + l = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + else + l = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt(); + return l; + } + + /** + * Considers the bytes a unsigned and calculates int value + * @param bytes + * @param isLittleEndian + * @return value of the bytes. + */ + protected int getUnsignedIntValue(byte[] bytes, boolean isLittleEndian){ + byte[] b = new byte[4]; + for(int i=0;i getDtaVariables() { + return dtaVariables; + } + + public void setDtaVariables(List dtaVariables) { + this.dtaVariables = dtaVariables; + } +} \ No newline at end of file diff --git a/src/test/java/edu/cornell/ncrn/ced2ar/ddigen/Format118Test.java b/src/test/java/edu/cornell/ncrn/ced2ar/ddigen/Invalid118FormatTest.java similarity index 76% rename from src/test/java/edu/cornell/ncrn/ced2ar/ddigen/Format118Test.java rename to src/test/java/edu/cornell/ncrn/ced2ar/ddigen/Invalid118FormatTest.java index d825503..c3a43b2 100644 --- a/src/test/java/edu/cornell/ncrn/ced2ar/ddigen/Format118Test.java +++ b/src/test/java/edu/cornell/ncrn/ced2ar/ddigen/Invalid118FormatTest.java @@ -3,20 +3,19 @@ import edu.cornell.ncrn.ced2ar.ddigen.fragment.AbstractFragmentInstanceGeneratorTest; import org.apache.log4j.Logger; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import java.io.File; import java.util.HashMap; -public class Format118Test { +public class Invalid118FormatTest { - private static final Logger logger = Logger.getLogger(Format118Test.class); + private static final Logger logger = Logger.getLogger(Invalid118FormatTest.class); private static final boolean IS_SUMMARY_STATISTICS_ENABLED = false; private static final long RECORD_LIMIT = 100; private final String dataFileName = "test-file-data-types-118-new.dta"; - @Test @Ignore - public void testGenerateDdi() throws Exception { + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueLabels() throws Exception { // Arrange File file = FileUtil.getFileFromResource(AbstractFragmentInstanceGeneratorTest.class, dataFileName); @@ -26,6 +25,6 @@ public void testGenerateDdi() throws Exception { DDI ddi = generateDDI33.generateDDI(file.getPath(), IS_SUMMARY_STATISTICS_ENABLED, RECORD_LIMIT); // Assert - Assert.assertTrue(true); + Assert.assertTrue(false); } } \ No newline at end of file diff --git a/target/Extract2DDI.jar b/target/Extract2DDI.jar index a8a230b344aba6962e92715af124cb474e992c1f..5404b24de085212336f84a70c69b1e18c899ee32 100644 GIT binary patch delta 11144 zcmZV^2V73w`)z6Ofu@FNDH_^IwC=sFoAwY|+KQ6&(xyFckx0m%B@_)ML}jOehOcB) ziq8oDbMJZSd;RlPA{0=N^ z;P=vc79JzEU56xsNeU-*3_@Qi9PlE!o-xov`}t_9vT;oMvA>Kar{j# zj5HOc#%ZP)_50M+f%-jZtW)T1#z&1q&1|S&KXbFff6X927!!$F7`GI3k)hg+_MboP z!sb=WiHe&TCoegR-_|d!a`~-|;4?@5cFxb-i6YOMYZ2ur#$7mJ&ci@h2m|k#Oehf- z?2#pZO)u0;i;#0c1^+?|TWA$n>|+J0=fw09XOA0bmEf0bm{gP5@j0 zxB>70;03@3fFFPW06_pk0E7XE01yQr20$Eu1c3Pf764cXKoWoy0BHa+0AvBk0aye; z9)JP>MF2_w76VWQumr$T04e}f0jL2`2cQ99830WHS^$;y`Tg?HO(&!a|1 zp4v9EQKQUobPSGO=AcG!gj%EOaI^zKP1q^EM@fhdy8yX<2g~h{Z)`ML4j1ujGpb#e z$B$`1p2|b05G%#qB#iBW8fp%s25>Za6rJIss4EvD%Q&b}og(4@CHj_P5>SiXG6aLT zgcTSD`SKp4*-*59CW> z!=$0+2VamZh%<^u7r>EA0_p-aSB~30gSa?Z>?*|RC}1WK7o&u2f|_}hkwuW|)`~ud zqZxI~8jdQoK)@(pBJZPeaI{|s(S=$b=^>iXmwPKQc}Sf!#OA}%q8{`XwDG1NwSyA* zoyZ_uN3TItft6a7h~b#fqpUPq@I1ntKo*lnPq;1(K{_B=$mGGALo76!7h7TO>cxcF zcAUGe-yUgVq1g_j*U9RzT3IDtmn~ag~Bu% zLhMdJwCWC}pu3=`$)qh~ALOk;te|KpiUq;Z_#`3)XVfZ#Zicfwu@v3NNzJ1u!)I`i zgGSpfKpY&l)h5%QFkusfi`G3!K)%B`rRbq7P#5;fb`8|mN1nrx9)YAOQ`BM{Yl1e27f3y{d(aWdfWDk|#rolXP<2Bb zp=QGoKl4zUfsW;*c(`a$wq=qg{iej&n1Wc%YnbwzdvAJn|=BRU3sSvQ0XL0qOg*2+gMLeI&&wQc-h zO{IzTAJN4`NFdHbKj6llARV~%SEIMhC6Da0-Zq^|b`8pQ4W>$MAFr=yz8KoRMXB$? zrmq?13ny}1Hcq@Q^X_Zu=jLX4cqDA~=4#A#S)tHsbYE>&(4pWEuCQHBW9FKxE01(< zNl{z1s$u1&0+)}f1rO{Sf4sOcasA)x-*>!BoT@vRDBGlJIG)bwL+9{Uz3XUx+~~C2 zBTvzI)fd;MyZsdREp`so!UI0XC#`Ha;=4OHvR1gG*7A|dU27!|+)?d`r0#~WYfmII z%c2yYTw4;NJnOI~Tzn7L7OoyJj+3X~bRrX$w z$HKEwiuKxsx@9FCDMSptYf;szM?R^vBpyj;ox^*CDt0#%`k9Eo)x*gEJ-*P)YT*PU8zj+aGRG*;YRKGYkj zvUu6*uR=qLVRA28k(Tte(rwK(#g8wUdUZtV9FWu*HcjE)8>O)Cb{m~fu0-VIJdNR? zPV2SUO^KbvsUS<<$}QG)-X>hxN1gRwwQqH+JcPB`cB($MF9@}*7ESBSy1V>>-{_)B zm4riXZ&=sXC+ViVQcCRDb9_*GynXX&{DO~2jo!^A0Xr_m&sZNkYQ=wN-!^fn*6MX< zvkzEU&Sbng=Q<_f*w}gZXgodICg5(b=zbYhy~@J(HgZF2^pACz4+K1nc^-H^x?axn z{JLiT4WoJj-AXpGhT?&rZuVtjqDpK*c?p)wfg8Q8+w!16q_vYT$(NeuTH(eTw&u=ObH9D{+=6jntpS9?<)I+IArdV=( zr1e^{<*OIU9aPduLBw`hOF0YAzovS5ZH@gK8;i+fdv7M46Kg2c5_z1mS4kgH6P3QW zy7t1&IwDv6XUY>NKVC zQFmkJd27iejps6QHEvIagWqPnWqGC?RbZmg9iAB;`)J5S^@WBPna~Ce8wd4pM3oTx(bK0s5#C?*N^sq*@Xu0{6*f=G z?38-sKh!#wXocOl;nAXd`>UA?!slu2<@ksVENn6JOXX0^03Vo4nI685Rz2d|G_FN~CO^ZE501<|FyzIeK% z=!D$CW?$7fpSHlGkM|l4oj;&^W$0~{LB|TG=cd}hGo{=D(TjibXfFK{B2G_kJh0RB z%>%5&t8we-hl-pVyASO6w(b2JsURu0`^~CPY~v@li~B|{6S*PtDP=(1{-{P3UdPYj zJ913FBP#Xb3cZLAABWez`KmdzSlqXTP8_&`g^DOF?tRO0!y(|*@O|(1LrHXpsdX3V z`~1~7dpG?XqIx5TKGbKHpVRI?^I&iFe9N#ootmB0^l}Z~WN}QU;6zhRmK%a( z%DrrARQVDs{%iLyrF;9$*#%wF{B2GWp>97+6AB|a)MRbN9&YM2;JLBZ%`~>_Z0Iz7 z*gwHDJa0nia&hDcS~_)isrjP$ogHy^a$P4Id2Un>eg9%6I}jk4?#ce+n*VtEmz>MF z!o4k*ZY|#|7ossV`1m_Z;_+8js)_1bA8rYL7w%_M8#TP5>T=S-$cAHmS5yL(H>}v* zsJiE?YU0_U`El8o)^u&k5tUWUBTf3`-EOMv12x_Uo%dqdFD(#YSJCzJaW=mgAZHF*ZJv%hEVk5kD-Q5 z(rlgd(zlaek5@#li&!S98@fY_u;xqmdb?5n1^=Bdx3VXt#GFr%u3p~lLD30XH*b>oMV>~UhlmD#DVE4YGmlB$s{-ac8NctFuYHDj5&t*T~ z6@!Y2vPHG!LK(hXt|iXSw$5T_S_8*CiX}uF7dFs?ubN8^RNdF{9qzR5l(sP~c`3a6 z%UGG2g&#y!QeC_}rp}42JnI}Rer8+5 z(WzxM4UdafZ0xJ54fXQu*erJ@aJ@ew^}SkKW*9wF%=tXW>tmkZC!40%C#|hMwZ^H6 z<@rCCFSMfPh6J37xw^Zz%=k_J8f?%hq9f5oe&0x>#=Z`-oYwL%q2)CtmM_NTpb=`(iZ!O-0vL}v9W&b-{b~x(ni5c~! z{OdRTvqXLWeaAvU_JDPnR%=F8^uHEIl$>}LJ9yHsQLK=D)xyjB-^UknPLy;VvvIt( z{3>xlq|BiIUjG$M^Urtn0w!(Ru3DD7aVZ~IsGcw1P!`>wA9786)-_*ccI-loz{1x* zS1I_^Ey2@tr=s?WKRgxw4I%bq?q4=|vE>}EsDFWkO6LL(qxYX*?`hsM`a z@d7a=EP}PufvL&m_EKN)t=wbxV@%E!&q(y%cE0!Sl;5@O>QO)FON)MtIy^VwF*~>U zh|?*r601)Sr;q=uF!XG8_x9~UMseOvAxE-yr}E8A#@8;oyYPzTE}nT3yoa>TE9Lqt z24#QgJY<_Pa^dr77t&9}Ys&!sXXFS%*4pc`u zom$Ww{&g((aqiHo#4|soU(h8^gx}aT{*Q5tvSDic8AHKgR@DPr(~spuu(L0ZVMz>p zji36c|8UpL^=e0r<5iu_hutpkGWakSGq3U=;f5b#VxL>;*(Ri#!*IK2MZ7)LDmiKO zI%{>DrTJ^W8JlV)nPsroG;QziVB7A%ThSieqM|M_CU2=5#J}BGB>3^>8oK2fpOAaQ zQ`2!Rnb!kv-B@OruwrPz%!!PupV9dq?v@H$Pe1%1oOPCe^LX{-RpVvlEiH}>x9z^y zTNgfk{H|xPw6y)Dm1Nf|Vfop;7I99e8h5EW<_@&<(D`PgWq-xH@N(&C?fIeEoHOKq z*_rozQtjyE%lX{Dzn&(>*wL6esn)= zYuOk1W4AvWsD3})fr+o}H$PdvUNh7cdw#a8Ctzo?Gf_=r6SdOn=<2b-%!iZtbugj zvhV2J68vN%E&aTtzf)XIn(vM#-O0+-*t0iTPd9C-SzaUZ_B@XadQ~Q*JG1uQv(ECj zLO0_}GhS)w4K2mXTb29M?qA{Ui;V}TA40XCBXMkuQhmO+ER|fE&6EPFxSAHh>;z6 z$G+!Raz+)&CS&%Ctn7#N543Z~v)r7~Z8&fvwQ$CwAvtk(?8A__x@zf(Dd*wq6_Wkq z?z{So)gtQyO+HM%YfG+Ri@O~yE%zybU2sRmO~s1QspF&Kr5x!Z3|9(k5r-M~A^ zO^;Y$zr%AXD^{k{w(4-;`K%wc`G!HFvGrTsY!CEL8NX}X)_X6HxZY#wxj=QJrbeqm z+TG{-i9wGv)t>5FF6|@d1FzQR{e?JoB|lr%Ob&l!0nsCBE|0p?xOKuZMc% zKGh^|mwIkQJh1Cco%h+*$}e{1(}&pX!u51^`sCvAwW2%PkexN0*`dD-Z+)uTbN&30 zT{{lM{(G@OIrv;n?&oNiFAZvAw1ImD(LK|zlfqc9UgjR@z2(rXx7(|aeM^|A;j_!} z5v2kTJstNHR*b$0%bJc>s;FYQ#<5O+{4495d(CW@+9!H5>y7On$nQOzCVPZ)>8<+O znFRyLJi5i!7(o;BWWFble#~gKE#&EbG$Wr9#ATi{#(OqQy0P|b*d~dy^IbpqmF!3v zjuXH(miHv4itwnt`>FCy-|2jOR%J_0Qbp#^bE1Y>Q30GUN^gJdA4)Je{>sMSm0hH0 zL<0Y(jqiGT4KCjF`uvoz$HQ}a$9S-Q~Ye_jsXu0V`0mLB3O9mRK^ z+qgf;HIc*a;a9#{$=C(~!3exy*!k9=7rz|Q{3YYUtunb?#};|7TpiYBtL-(dZaV+S z)~+G7714Rx(T^iV*j%Fw)2;~3U!nK@o?_Wc76*irYwpF5Msq+|SjE`Het)wj-p62O zf)Z~>2$Onw_?5Al@! zpKT2LdAERlk6YMFHZn#O&7~bP17E7siFOQt9RPa(4gehMh<2yVd6YtSH$0RYM7!b1 z)EAbD?^&lJ!RHVKP8tm(INfnmB0?S&A^O5m0V2rNj{0Un2-9&XR?8QJyc@oR==Q{g zi2yn-M`_v@3rH!0kksUbFC_L`qv8Z#91caL;sE{9$RAM=;*zI>A|)CU1W40ke@Mba zQxLAah+O!&FTXUJ<$o>;j_g)_5BXQ6BjQBUZhRiWm4J&-Lh>dPM@zt$$3tK~_Dn*h zQ_Mp06#r7%N@-cZyAgb0TE)bFYMSCNY%<{@ehOov#7|KNK1#+jh^Or-`1r{RWb~P2 zR{lFDqm_iur>1C|1g04MZH{6ufp8HON%#_q{N5XoH?t9yfw(4N;Daxsume5-yPu21 zE-pM|#!IOB;)aym1a2Bli%BL;fs~Oax*QQ+ zqI(l#Lp2(-_=XD5XtnY*njsValrrR3&S0#heF2zan=Zs=7vPGN!6x(suM*fT)4)k) z1<8gaD|H7)?AZfk1x-kVCL#D0Y>_?O%M;pZkiJaJ8m|H;Rg*>%8|Lts%2nD}`yk{|Bx+$_d% zIgte-(e5xu0XtuqNCaOv388FUk~$q4*?@-=pW}(o#?>fenJ4TnRsiRu!DN_EN4V=8 zJ9Rn)x8mxQw)$kCE#KoW@}X8#iI{L_90ZLVz!T>47mu+9$O}f#Fqr11)&rg^esesg zez-p6760Ut?^l3VRlqA_rV!Nn&#|Ao5AyAzREEXven8u^pgA(QIp8Nob&&aU$MY-6 zBN#HrL+y3D4UC;L^L*3#6Tqw_FpDvnHIJlZO7y{r{&U#n@_;t~=s9hgMi?()l*DjI zI1P~L+bA;e`vmJ!^J`hwAJGQ3TrOBtk7|Y z1xHYJt^(aTl0I4nd`$uIMw$G5aF*f+R~p=es~4_7Me20+{f%V$CyTrb6Z^<{l6^6` zoMMFgCR~lO{5Bt0UUrEj$&*)m^NH>O#vYf80Hm^7Nb-)rr6~ifEn}}af$|mqR}t&3 zQvML#TNuatJ{bX}>kvD!0~aOKV{j45?7Kif+S>|Azi;AThhe%EkjC#q5|xf}$pUxR z#0iT2bO-bg_x{kiiK17w@c2>=hI@}3-2xnR2Sw9}DFV9Plu$`5Bei&ug;(e?iTqP| z=MzmnxFn^>lmD3L0-)rf_}}@(_xYR*$S=YKZv(o3Dwo`3H*-(gy6`G-E(;fDogHN2 zTsn6GHB*6cgONEhd0R+;+d_dkp3PJt9iw>u978GM@die{@e-0f@DDgW)!=_9m=AsN z_?%)YQS{|8&LRuB;GZX$$S~6ig#ilpCz2vh-ajf(E0sX*f}dYWtY*m$aFXmlA@isT zM3Kk00CM{eicGeW(!^T}Mi&`RE|do=QMm;i0I-n%X;Ml(wWgK>$2Wk=?98-A%Jcs4 z3ll%-s04KqC{tI-jqw~bJ`8=r#4~+Vbgmd04Y8@$gY@SGZkp_vl)mTtkI-8~REXH& z;Q$l44B5{ri4v8&aD8GT7PJpP)_@9_8OvyWx)!DQ9|Y&=AlOmN6|)7jKBUk99bZcA zmvo69Pj)b=ePB||_6HY0R?zQq5E(_l>9-r5p#868GvFul$G$Lm?+P*K3(6vx2PqOhf-E3}A{eeQ z-F7osG0T9i9WG*|BBMzK9(}O1+F)s!XFZw&S;4INh+R?ma#nf~iBTe+MKO9FEFP?G zUoqqZ=bDQsjK^gOwk@DgZK_6zrUE$?;zc`ndtFqFvlAg(80~Z9>Qq7zMBxkwH*@H0 zkCXe)nc6#F-GKJNQ>1oH_&*g!zPKRKwVRRYuaO1s+ZjlwvH;nPQDL6DoBz9emA&xi zKyZ;a^{_G|cmY+tHUA+U{AepMw^uJVIJo2khxI|yUc(g7Tu^Ct;ZBs*8&k_2qX1Vf zcm=j#;wrcey#)Vv#7!W(@rBd`-;l4ZF`(X%69goFVqH3DZpa>-qOT7I^ytq2(60tF znlhI!`!Br$8&3p8kohpsA3=Wk&A0@mC5(JEY<&u`;G8a=JJ+Me&96TIE#^RrIg`WV zpbn8<$z?J6b)N_GKL&tZI41M=TawiPJgqY4P$?qOmyx31`2wp_<0Q$P*r|fR+Czou zn7)WhJ$Z=c`RK|1+AfQ0=WM0TzshLF|EmV;={1(~-gpOJa-kTTIM5TQO` zj=AU7=O*&^f+PZ7(Wv*`dBnRoMtU{pKV@bH1V(^=%#GDDK2k`N04)n_l4KHq)lp#* zt3`yzp>lIY$6PiwKL;$#Hle@w@U$q@LbWj9ReK)s#)Gk?ShfRwG%4sBXiw#+h(8#; z^y4nrn9U&T=rTpzR2H)C>jj-B9~Yx^`H@;e6d>u~c6=oj!RI8_SqWmP+&140G$()F zZo0@;c62dCCvy@HE2y!5(znDNTlHWt9Ukx+%^Py`Z4`znk% zfp8Q3A`aC5WfDAOZM$Mk%26-6oP_rtT!r#7%M*~a5J>9Y1s3P-dt08l_bYp{f&Yj> zli+_%q$w$f41hJahuEA9#?dhg2UvAih^1zn70!4~pDhF=VRwo|PPWkW2^I(8oEe|ZP<_A5 z%=uwM7^;wmRmlEdRRo%7f|#Q?-=hE4oUzzkE8YAwGmOR=b6fZmKWct>)SL-|8G;3Z z6@m?d9fAXb48aM(1;Gum2!aQK7lIFBF$6z^0E8d}1wsfy7(xVM34|!bQV1~!aR>FIN-95NTdf~m71ZYM@Xr9v|1fOMlaV$w)X@jX?Njgy`w5e+Gi8bK); z(+4m~IYG#KDjC>QXzZZvIL-;O-EiK@1AN#36JfQ%6Psn>rR(vleOOz?L?XGffC)=X z352-ei@@h3d{L$5Hf#(@iw9sD$Ons2Y!fpNT`zDAOTjCs_XK!EVXMHL2d-IpZ7DW` zG)l#>8sy<*Ic9~t&XC3YS&7=q4q=NB_YaLmMH=@Ku^*`PDwui%h1@!V>c~oX`7*2A zlEh0Q8AxXgu3naI&I(F`ae>N~3~VVcAz&SP#i$Gla7uy+Uz%1dcl4&WyZ%Tue>?g1 zLr-R)LLAAKTyN(k7Pux(++VzDIqyxuA**u@FRZ)2(572Xo~Y++uiN6V8jb+& zyvKdT+p>l_i$5*Ni|2WMbvNHP<@`;zuRcvpJLy;$)pqx)n&FDw?{Z>qy*7TW|9X$f zy`L>*_a|wjjZX_+gwzz}#+t5`Ng}_Ey8heM+#>m)n9iqH7dh~R3#%+!t=+6bo_Bsz z3E@}oG9Np%tmhbA|(LJ+9%YLnB3+H(GxxN$DqA8C!Iy-l_#fDf47gV!K zMhdOFT)1Y{c|Z2)4ekLI4h8%jL5&G@3997&0KegiYhllHjFmc$9L(N&LgldIHEwS5 z%F#x)Du>!NLL!4hr%KYzZL)Md55=gZ`_;T%^&>%#<4s(S%W^m8)}u$bv7Y4kQ0>e4 zo2$-r*G2|DA>Vc|cbJZ^wKm9$ynhRDNCe$A>}&$xn+1w#P*XyxOf?Xvsfe*|dHixmgW(FzWRGiF8&@%1 zbr)+=OMlnu-;=xS@U80!ViE$=ZlfE@Z5sSu=q>vlm>H-e*DFlRJ5=(kUnc!~;pkea zDBmYXMx_*j?u%`zpw;dcGAz$156`?48#FQ{AMrzv_s(NKqx0EHya#t{OP(~YyLxK4 z-6<&j(2Y6y^Sg!9N;XLMef1Jy&m+I2PISF7aiYAh3l=g_O@NB+o)nv(te1(xe9Y8_q0J%R7FRGxbkA*w$W@-8LiN$**vQE)Ie z)yLGeAtvLAMG@D& z*l@duNDf`JM~O| zSS4;-N2hm@yWHgnQU2XwT5}W)#TjiOjiLPBkqq_60~soPxyKh&QMdXHV zS!Sr>p)^((vF=xOmvxol_TxTQdcwM2H>ee^2YQvGZ=seeqjvGQ`aDR<%dTD(`}OFInoos*gRIAOB0m(_GTLja9Tg#fbbYEZ z;9rzKr2|`)Up2-~jOyGr{3t)#O*QX-Y-~`S^Tb*Eid1~_Zyx=X;(I?2jBw3i4YAm{ z&K#-gv(D*3o2yc`IMr(Ocy#RA40L2=PSw0VSraYt=gJU>%(-ZR$xkl{E6MJ7c*^*Y z+z89L*jFDi(ucpQ9uxm@{rZWc>wYcuz9X`7*S9QVJJqup+1r2WM<=e^X&vIBX&FwWcCX_j|EuS3-%^-h8v#j(kEUKq}LMR`xMXx%ry+V@st zAOhE4Z?ot-=j$KJYK!l!*RNPPW-sC<{qg(M?}nSxhSwePlM`|c4%eJEGw^MofC-Xu z4Ta^oYVgx)fBwOtbK#nW3K{WZ);>KuZY#bSal7A|wYlVVF1FrPR@7rlJ^QfwP=Ljr z0Q*0zrTpA8vB}R@Ul;z;OrL~enItno|ljFQ2BjtGL?S) z6kSH+4ZSg08CqPv?rX5CdAD--S=*drO#Ru1GovbZu_V^V)+?$hmt5R%n?FtDp3o2W zi=rxOH!2EKzpA)}8*7|?FXmR?buq8#UeU|WIsv`nm;j^tUZ>@tZah4^GWc7PNxoY{ zysOWP4wo%v+aC^0j$G}%oEl#1XL;21W%;e;uYcdn@#V7Y6~o@`?09=DE9TTtrqna1 z(`U!m8x18+MKY(wZhu!U)M6%lqt806psRe#%?1m>ckdsEVQ<7rRu6U;eYbcOrm)kp z<^8XzLdyBXC=Smbfrn1t$3o`Av7vRj zja{l&xPp0uc32i~)t)m=y|mO3>`6VVdb4T2GTUiUzn@W436veNm(;GcFU@g@7g=`I zV|(Uf8P-o_21A$L_@q?GtEW8*G|IHUe@=U6F#mGZhcN=D9$u+B?&P~|sdi_Y&Nu64u|!;~e{N&(s}CF=ruHiiSAgPGxJUH z#HRws7y4YfCS9f#Ws!T>^B=Y4)+O%mANkhWUeo=!9h(tV9B7br51*Whbz-7?8Cu~& zne-}BrMP$cl}EGkFm-F4cqk)UY%r!GZYG^O%Vil-^09hj%9c;$=Nz_{Piyb&Ah%8l zoo#TtcVUsi(V_A(f7VTJ3QQcdlHB_D2^ECmx!1>4Z9lAdvSf^`9Gx#Zy45kz`1Zc{ zGl#55=357Eo@>1!AAk4c5tE&W_8aFct$m4C z{XB84=N+!fsr!?1+@V5#dcDs?zue5({Y@kK@m4cBgJ1Ww4=4_jGh5y(v9F(1)}Hao zZP9Jt`1Z=gdf$nUqS3vHB~6Vn(kp&B);NuRV8VWx@SbS)mUn2A4HB&#RgNvQ|7~#Z zY;CWc^U5U9>mSQs_-%c+%U)+?zX@>QvUjJ#k=HjC^ZitAyI@i8eNA(^%s4i?_I1wm z9@h7#66TUkttOWTyq_xH?^`hWT>tPNu}A7Tt0*OMeoMj`9F9LJkq3RE%Y2uigGi512(nX z=hT(@IY;H!=Uz?=JeodNpAndIsbz=nsde_AL)kuFJ3RD!e-0%EZFr2$EmC{ez{=g0 z{k^B8%l-G9t?SZuhZ3-%M?Pgt#UxqTqgvvrm45momaZm;bxoOtEe`v>Q9NRAG>o+r zojDz7r}D;3etA{Vj&cXNG{z0m+&F^TayxjbyuqR zP3nbzvEH3wo1Ngl=_GU0QFQk)4#U{y!)`{a_$I=mv{y-62Sm;W)=CGNX5HL!#7k2# z)i|8*+p~Q~o<2Cz;cfjSqsOXU+Q!53wA}#dY^~W|rU%pe#4kts@l&F#~e48;T7f!3a z&s#iJ=+$51*CTzoH|~Z@PiW@gDlYAhdjfBIwh2Wwbv*B&#A(rek=R_U*?R*(I= zUhL=A_bMuzJY0HbzbIcRD-PV}>-#|^^5#9EHeJOQ1Ilo@wpr+*>d;DUDretefsbL5 zL!&XA%B%FwRR-l*IiistXC-7L^Jdo{zD&1~<`p96Nm-wr+@!qX<8oq1P0{Puo2=ouub3CT|Iw0Z z8R0JM(^1st934Z^*Eu-I095cWY(eWD8 zV{XK;`R17IN_5UX&`SM<&eZ$csGR5opVC2HgU;SFhSXj*;_w{zRZdlEIf<0C12}Nf zmI2c`cx~_}1{bN6Swq!dPMp~fPE(7Kg_X0^OjLUOhk6*5F73kx(V1QNEA|D|j!Dv# zQAs+0dK=ZLzmYbCJUSCaEm=fZzH|ccX7Q$CVaW3IG8#XH@Yirc zmP?O~MAGKTIKFxr;N3xmw-raJH`swpJWk2D&Z>|hZ6OBg3#iA48tSXD?dY~6s+g)E z&PpQPC4*zrR3+eGjPZk#K3i@;+kn`SvE_7P+FgxTPM=4bX zmA7nL8CW*1RcvJ+Q#!ESHzce0R3F7Qs$5O}__)vVnA1UrpXwFIfzsC0bL zRuz?M-q;!=2i9`ZrjR)vF`6ObM#|FOq3C(a(~hCi=t=4!exj8d3#liNoPVF~C@L8) zqTNLILz{a(OdJQlTQEKPWvO(flK|}sve06IEkQNCwZt}{IAv|poYaDG^5O&)`{1pR zX&NpDtoGu>2XH@(uQrg%I{! zap8^(>@Hux)JIj$$Ti^xM?IkL>>}8F52WnI8j+*xqcA(9A1aMWp;A%;CW}gQ%dw}t zgrj!$v{95kWCL0=JCVPaJ2yQ20q@R^u!2c0sy5hh7vlgm$8m{D1sj?mvLJ4ZiKDR< zEI}g}4>Wo4cTT2k;HSTS0i0%?}3P zJ$a>?BlZS$Jq5>nkinl^m@q2sb;qoc8O|Zh06F8skI_&ib|IJy>gtE;s>iOu$X;gy zf0m(1gAY93j&oPu3Bw+vOTx{ICC|QtX;c7$`QasDxim(u9D9$6plN4xNw`uGqkTZ$7+%BtnTb)gX1qto73#6^kVrc83vqC?9^QDyaMH+?f;!k< zr0M2QtwG-3O2!;fd$;YPzCa~O7g{~aAL=_i2{@zJ~sOsnpQi2y$`_ZOebGbJo&U5p=U!nhR0TdO#BcRkbv$e_s-H zG%KV-y9ix~tD~6{%!hR}zrRvme+y!u;=#PKV?8t_Tu;*@c<>8HR!px#D|Pj>l>{Bu zECwhIwAG0D`7mULG|*ndR|VkMNOOh1hf5l15Oxsu5DpNIl_iZ%+lg5iEZv9)5~dP2 z;_*aBc6iATX2Efg562u1B%E>LA%`DK9KzVa92w&TeJg@4MoE^ zNTdz_d3VV>5dOjz9Jo)@1-$OKDpAM%FkFnqbYPwwu@%=O$aATXe0bOvJl%nt(67e@ zL2w#QBmyY&fxHv%=T&&TFgcLxGtc{Y9P)xc*@E+fwz?nzqX__4UI(P78UlHm6Z1Sc z*>M2XGK`PzHc{v4gOG3jh2Wcj?+5Xv1bNdwT#R{!33!I#8eo$wRh;ldn%+?Nf#-^s z;LvhnhhS(Zt1y!1>!vRI=lT83(3G+$VsX3S(nJO2;gGsX5@>D1wZINPd@+hEW!Jm| z>TZyKS?+)NFzzB?amu{&XZ)bh?`3o$P4uu}S4x#80zkqZ@dLk(yYJ~iCPHx z07=0~9_VW{CyAuV;Ok>cy1Wd1=fe%!WHEMC2e=LSJ8jWZXvc7PU_MNtZ7^^~47z$4 z(uHr0N+={Wt21Aqh&FUhgF)2F4vFTb{ftZ6Gv64Yv!kLh#Kk`IJQ2D{q1M|5U5DaS% z-MqksPe77H3;w%3R${Q#xt)+^rg?(nUmPifbPctLTONjH3f6AGAlE$s!4wh=>W8+_J-iov-C+6 zbqR1eWsj!&v{1->If1lWLveAUgE~_(r9EI@|AcE7<9Pfzjk;ny9zN5~Zo~zMmMD8e zEA=yoMeGmvufgQ$`9PRUT0W602>ZGkb^&8d@Sh8Q*~0Nb7*jn4jWsg?rf6J-?r`w2 z`N({tx8$V|!61%N03M?_!0I?$nR%BOoh%RTpMdjyX&ihNhKDOj=BMJoEFO0!?0$f) zW0IhYX#m}wxClY~90_g8%OEP@`QP&u%D*=HD6U+F_)B-p=bz(FNIj#tpd|{7M`$RZ z20I{&Srf5{@g|l(Kh%g=lq^Y2&4v9(g(=CHj<_~bIu-^)%eZbi7yWvc2s=Iw_6g&< z6``}Bq)Id>a4)PsN^gP651RMlQYZj))=`81u#WD+xj?dug8<RVTx$)KKl5|b3%ML z|8s)+wE1bp^eAKvtw$_4Xmp7HJbbd>S_+1F73?2NhPoeZToC00x$?9n|HjwdIB3e< z1@Yn7CIjhAssN}-a6ri)vgRO=gAFQy1Ax)!$acDt27qH}8R0OWGc*+wjHrK;Q4~b| z-J5a3{UGzUoeO+Hkg|xj=wIxJQ}h3sCbE%82__^;lXCiqRo}Zn9dREn_OE}h_;1KX7Y!^pV9T%1l9MVqC=!abW<;mms) z&b*A7qj3Tph{iV)wyxfB*l--$(Su2A!Qf|E7Cpb52&sFMT8^Pm>L|=Ya|WraXHaY4 zg2xH^56!n`!WH^`B6oqs112VIg%%dX{!NfwZ~?IuHqXyJX7r8la6Y2KJu(^W(1G*W z<5^n@DBnCk9^9XbeDQzT|} z$inIuSQM~u$dIP5nhFmWSQ5a2LSq9tpQ*h6wvdMsd>(v) z)c^iF4!p10J-_@e+)}{YFk|pPzVn|scyWQcde588x;3y9E8#}Vg+W}f2h~xt8`JsM zuweQ^{Ne>-l?UMHgn5g;w^e(Ms15;`AmH4wbz?y|y%k*K-?CPoZuBGmWOp%v>dh!tZG_Xnb$^BpGKY^zG&$8te)jEkN2ERs+4 zr!EHOZVqVe45A;_{*Y+E>@K`?fcGk>GjDl8asAA-a`d#N2=OCDTEZ6u66?IU(36~JW| zGxTMEBrPMTjdvoRET!K_luMhuof*i zpf@tq(x!++iT^NLIBQ>qLxE*P^&czKe?B6u;?^0}aGLRh))<%O6`KFV(f_Ks49%ZA z2_L>Z=`QMmS(^&8mT`UFu>xsQJaFXhbg$2rA{fP`F!YR~_jQqIY6vFvZ{+RY4&5C# zKrDD+g|;om5s>=ZmvSwgKBDkg(- zY&7wIi<>gs93nJW|9kR-J)!fe3rD+&>PO9Q-C;vrU|bkC z`A2Y2ehi-S&-%kDFDiCEz4ninGrxiTw-xrl8iszvHqbz*JWT`qNX96HZ$vxqxj6F# zK;dVw5=#b!oQ+UnKaLYaH8lhJ8RJ6uN#9PkXW(*#e%8r&_z#D0Hw|Z3H3t2)eyCMX r4&Y1acWsg^p8{dz3aD11kwJkD7#q|gks5d#cx|NFNw31`{rLX?cKFf-